Merge branch 'main' into DiscordBot
This commit is contained in:
commit
0da6577f43
|
@ -2,6 +2,7 @@
|
|||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
using Moonlight.App.Database.Entities.Notification;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Database;
|
||||
|
@ -34,14 +35,13 @@ public class DataContext : DbContext
|
|||
|
||||
public DbSet<SharedDomain> SharedDomains { get; set; }
|
||||
public DbSet<Domain> Domains { get; set; }
|
||||
public DbSet<Subscription> Subscriptions { get; set; }
|
||||
public DbSet<SubscriptionLimit> SubscriptionLimits { get; set; }
|
||||
public DbSet<Revoke> Revokes { get; set; }
|
||||
public DbSet<NotificationClient> NotificationClients { get; set; }
|
||||
public DbSet<NotificationAction> NotificationActions { get; set; }
|
||||
public DbSet<AaPanel> AaPanels { get; set; }
|
||||
public DbSet<Website> Websites { get; set; }
|
||||
public DbSet<DdosAttack> DdosAttacks { get; set; }
|
||||
public DbSet<Subscription> Subscriptions { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
namespace Moonlight.App.Database.Entities;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class Domain
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
public SharedDomain SharedDomain { get; set; }
|
||||
public User Owner { get; set; }
|
||||
|
|
|
@ -20,4 +20,5 @@ public class Server
|
|||
public NodeAllocation MainAllocation { get; set; } = null!;
|
||||
public Node Node { get; set; } = null!;
|
||||
public User Owner { get; set; } = null!;
|
||||
public bool IsCleanupException { get; set; } = false;
|
||||
}
|
|
@ -5,7 +5,5 @@ public class Subscription
|
|||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public string SellPassId { get; set; } = "";
|
||||
public int Duration { get; set; }
|
||||
public List<SubscriptionLimit> Limits { get; set; } = new();
|
||||
public string LimitsJson { get; set; } = "";
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class SubscriptionLimit
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Image Image { get; set; } = null!;
|
||||
public int Amount { get; set; }
|
||||
public int Cpu { get; set; }
|
||||
public int Memory { get; set; }
|
||||
public int Disk { get; set; }
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Moonlight.App.Models.Misc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
|
@ -7,14 +8,21 @@ public class User
|
|||
public int Id { get; set; }
|
||||
|
||||
// Personal data
|
||||
|
||||
|
||||
public string FirstName { get; set; } = "";
|
||||
|
||||
public string LastName { get; set; } = "";
|
||||
|
||||
public string Email { get; set; } = "";
|
||||
|
||||
public string Password { get; set; } = "";
|
||||
|
||||
public string Address { get; set; } = "";
|
||||
|
||||
public string City { get; set; } = "";
|
||||
|
||||
public string State { get; set; } = "";
|
||||
|
||||
public string Country { get; set; } = "";
|
||||
|
||||
// States
|
||||
|
@ -34,9 +42,10 @@ public class User
|
|||
// Date stuff
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
|
||||
// Subscriptions
|
||||
public Subscription? Subscription { get; set; } = null;
|
||||
public DateTime? SubscriptionSince { get; set; }
|
||||
|
||||
public Subscription? CurrentSubscription { get; set; } = null;
|
||||
public DateTime SubscriptionSince { get; set; } = DateTime.Now;
|
||||
public int SubscriptionDuration { get; set; }
|
||||
}
|
1082
Moonlight/App/Database/Migrations/20230402204329_AddCleanupExceptionsTable.Designer.cs
generated
Normal file
1082
Moonlight/App/Database/Migrations/20230402204329_AddCleanupExceptionsTable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,170 @@
|
|||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCleanupExceptionsTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "State",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "LastName",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "FirstName",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Country",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "City",
|
||||
table: "Users",
|
||||
type: "varchar(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Address",
|
||||
table: "Users",
|
||||
type: "varchar(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CleanupExceptions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
ServerId = table.Column<int>(type: "int", nullable: false),
|
||||
Note = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CleanupExceptions", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CleanupExceptions");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "State",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "LastName",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "FirstName",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Country",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "City",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(128)",
|
||||
oldMaxLength: 128)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Address",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(128)",
|
||||
oldMaxLength: 128)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
1098
Moonlight/App/Database/Migrations/20230402215155_ChengedCleanupExceptionModel.Designer.cs
generated
Normal file
1098
Moonlight/App/Database/Migrations/20230402215155_ChengedCleanupExceptionModel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChengedCleanupExceptionModel : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "CleanupExceptions",
|
||||
type: "datetime(6)",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CleanupExceptions_ServerId",
|
||||
table: "CleanupExceptions",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_CleanupExceptions_Servers_ServerId",
|
||||
table: "CleanupExceptions",
|
||||
column: "ServerId",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_CleanupExceptions_Servers_ServerId",
|
||||
table: "CleanupExceptions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_CleanupExceptions_ServerId",
|
||||
table: "CleanupExceptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedAt",
|
||||
table: "CleanupExceptions");
|
||||
}
|
||||
}
|
||||
}
|
1067
Moonlight/App/Database/Migrations/20230402220651_RemovedCleanupExceptionChangedServerModel.Designer.cs
generated
Normal file
1067
Moonlight/App/Database/Migrations/20230402220651_RemovedCleanupExceptionChangedServerModel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemovedCleanupExceptionChangedServerModel : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CleanupExceptions");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsCleanupException",
|
||||
table: "Servers",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsCleanupException",
|
||||
table: "Servers");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CleanupExceptions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
ServerId = table.Column<int>(type: "int", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Note = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CleanupExceptions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CleanupExceptions_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CleanupExceptions_ServerId",
|
||||
table: "CleanupExceptions",
|
||||
column: "ServerId");
|
||||
}
|
||||
}
|
||||
}
|
962
Moonlight/App/Database/Migrations/20230403130734_RemovedOldSubscriptionData.Designer.cs
generated
Normal file
962
Moonlight/App/Database/Migrations/20230403130734_RemovedOldSubscriptionData.Designer.cs
generated
Normal file
|
@ -0,0 +1,962 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.App.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20230403130734_RemovedOldSubscriptionData")]
|
||||
partial class RemovedOldSubscriptionData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.AaPanel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("BaseDomain")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AaPanels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("AaPanelId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("InternalAaPanelId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AaPanelId");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.ToTable("Databases");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<long>("Data")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Ip")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("NodeId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Ongoing")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.ToTable("DdosAttacks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Default")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int?>("ImageId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ImageId");
|
||||
|
||||
b.ToTable("DockerImages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SharedDomainId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.HasIndex("SharedDomainId");
|
||||
|
||||
b.ToTable("Domains");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Allocations")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConfigFiles")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("InstallDockerImage")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("InstallEntrypoint")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("InstallScript")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Startup")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("StartupDetection")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("StopCommand")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TagsJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("Uuid")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Images");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ImageTags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("ImageId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ImageId");
|
||||
|
||||
b.ToTable("ImageVariables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
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.Node", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Fqdn")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("HttpPort")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("MoonlightDaemonPort")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("SftpPort")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Ssl")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("NodeId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Port")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("NodeAllocations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Action")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("NotificationClientId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NotificationClientId");
|
||||
|
||||
b.ToTable("NotificationActions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("NotificationClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Revokes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Cpu")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("Disk")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("DockerImageIndex")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ImageId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Installing")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsCleanupException")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("MainAllocationId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("Memory")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("NodeId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("OverrideStartup")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Suspended")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<Guid>("Uuid")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ImageId");
|
||||
|
||||
b.HasIndex("MainAllocationId");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.ToTable("Servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("Bytes")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("Created")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("Uuid")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerBackups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerVariables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("CloudflareId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SharedDomains");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Answer")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsQuestion")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsSupport")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsSystem")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("RecipientId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("SenderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("SenderId");
|
||||
|
||||
b.ToTable("SupportMessages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("Admin")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("City")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Country")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<long>("DiscordId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("State")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("SupportPending")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("TokenValidTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("TotpEnabled")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("TotpSecret")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("AaPanelId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("DomainName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("FtpPassword")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("FtpUsername")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("InternalAaPanelId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("PhpVersion")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AaPanelId");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.ToTable("Websites");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.AaPanel", "AaPanel")
|
||||
.WithMany()
|
||||
.HasForeignKey("AaPanelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AaPanel");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Node", "Node")
|
||||
.WithMany()
|
||||
.HasForeignKey("NodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Node");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Image", null)
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("ImageId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain")
|
||||
.WithMany()
|
||||
.HasForeignKey("SharedDomainId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Owner");
|
||||
|
||||
b.Navigation("SharedDomain");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Image", null)
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("ImageId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Node", null)
|
||||
.WithMany("Allocations")
|
||||
.HasForeignKey("NodeId");
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.Server", null)
|
||||
.WithMany("Allocations")
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient")
|
||||
.WithMany()
|
||||
.HasForeignKey("NotificationClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("NotificationClient");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
|
||||
.WithMany()
|
||||
.HasForeignKey("ImageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation")
|
||||
.WithMany()
|
||||
.HasForeignKey("MainAllocationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.Node", "Node")
|
||||
.WithMany()
|
||||
.HasForeignKey("NodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Image");
|
||||
|
||||
b.Navigation("MainAllocation");
|
||||
|
||||
b.Navigation("Node");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Server", null)
|
||||
.WithMany("Backups")
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Server", null)
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId");
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Sender")
|
||||
.WithMany()
|
||||
.HasForeignKey("SenderId");
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.AaPanel", "AaPanel")
|
||||
.WithMany()
|
||||
.HasForeignKey("AaPanelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AaPanel");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Navigation("Allocations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Navigation("Allocations");
|
||||
|
||||
b.Navigation("Backups");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemovedOldSubscriptionData : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Users_Subscriptions_SubscriptionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SubscriptionLimits");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Subscriptions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Users_SubscriptionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionDuration",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionSince",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "State",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "LastName",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "FirstName",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Country",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "City",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(128)",
|
||||
oldMaxLength: 128)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Address",
|
||||
table: "Users",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(128)",
|
||||
oldMaxLength: 128)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "State",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "LastName",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "FirstName",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Country",
|
||||
table: "Users",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "City",
|
||||
table: "Users",
|
||||
type: "varchar(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Address",
|
||||
table: "Users",
|
||||
type: "varchar(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "longtext")
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SubscriptionDuration",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SubscriptionId",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "SubscriptionSince",
|
||||
table: "Users",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Duration = table.Column<int>(type: "int", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
SellPassId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Subscriptions", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SubscriptionLimits",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
ImageId = table.Column<int>(type: "int", nullable: false),
|
||||
Amount = table.Column<int>(type: "int", nullable: false),
|
||||
Cpu = table.Column<int>(type: "int", nullable: false),
|
||||
Disk = table.Column<int>(type: "int", nullable: false),
|
||||
Memory = table.Column<int>(type: "int", nullable: false),
|
||||
SubscriptionId = table.Column<int>(type: "int", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SubscriptionLimits", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SubscriptionLimits_Images_ImageId",
|
||||
column: x => x.ImageId,
|
||||
principalTable: "Images",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_SubscriptionLimits_Subscriptions_SubscriptionId",
|
||||
column: x => x.SubscriptionId,
|
||||
principalTable: "Subscriptions",
|
||||
principalColumn: "Id");
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_SubscriptionId",
|
||||
table: "Users",
|
||||
column: "SubscriptionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SubscriptionLimits_ImageId",
|
||||
table: "SubscriptionLimits",
|
||||
column: "ImageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SubscriptionLimits_SubscriptionId",
|
||||
table: "SubscriptionLimits",
|
||||
column: "SubscriptionId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Users_Subscriptions_SubscriptionId",
|
||||
table: "Users",
|
||||
column: "SubscriptionId",
|
||||
principalTable: "Subscriptions",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
1005
Moonlight/App/Database/Migrations/20230403131343_AddedNewSubscriptionData.Designer.cs
generated
Normal file
1005
Moonlight/App/Database/Migrations/20230403131343_AddedNewSubscriptionData.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedNewSubscriptionData : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "CurrentSubscriptionId",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SubscriptionDuration",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "SubscriptionSince",
|
||||
table: "Users",
|
||||
type: "datetime(6)",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Subscriptions",
|
||||
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"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
LimitsJson = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Subscriptions", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_CurrentSubscriptionId",
|
||||
table: "Users",
|
||||
column: "CurrentSubscriptionId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Users_Subscriptions_CurrentSubscriptionId",
|
||||
table: "Users",
|
||||
column: "CurrentSubscriptionId",
|
||||
principalTable: "Subscriptions",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Users_Subscriptions_CurrentSubscriptionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Subscriptions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Users_CurrentSubscriptionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CurrentSubscriptionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionDuration",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionSince",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -482,6 +482,9 @@ namespace Moonlight.App.Database.Migrations
|
|||
b.Property<bool>("Installing")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsCleanupException")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("MainAllocationId")
|
||||
.HasColumnType("int");
|
||||
|
||||
|
@ -606,14 +609,11 @@ namespace Moonlight.App.Database.Migrations
|
|||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Duration")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
b.Property<string>("LimitsJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("SellPassId")
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
|
@ -622,39 +622,6 @@ namespace Moonlight.App.Database.Migrations
|
|||
b.ToTable("Subscriptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Amount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Cpu")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Disk")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ImageId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Memory")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("SubscriptionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ImageId");
|
||||
|
||||
b.HasIndex("SubscriptionId");
|
||||
|
||||
b.ToTable("SubscriptionLimits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -723,6 +690,9 @@ namespace Moonlight.App.Database.Migrations
|
|||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int?>("CurrentSubscriptionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("DiscordId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
|
@ -752,10 +722,7 @@ namespace Moonlight.App.Database.Migrations
|
|||
b.Property<int>("SubscriptionDuration")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("SubscriptionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("SubscriptionSince")
|
||||
b.Property<DateTime>("SubscriptionSince")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("SupportPending")
|
||||
|
@ -776,7 +743,7 @@ namespace Moonlight.App.Database.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SubscriptionId");
|
||||
b.HasIndex("CurrentSubscriptionId");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
@ -966,21 +933,6 @@ namespace Moonlight.App.Database.Migrations
|
|||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
|
||||
.WithMany()
|
||||
.HasForeignKey("ImageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.Subscription", null)
|
||||
.WithMany("Limits")
|
||||
.HasForeignKey("SubscriptionId");
|
||||
|
||||
b.Navigation("Image");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
|
||||
|
@ -998,11 +950,11 @@ namespace Moonlight.App.Database.Migrations
|
|||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Subscription", "Subscription")
|
||||
b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription")
|
||||
.WithMany()
|
||||
.HasForeignKey("SubscriptionId");
|
||||
.HasForeignKey("CurrentSubscriptionId");
|
||||
|
||||
b.Navigation("Subscription");
|
||||
b.Navigation("CurrentSubscription");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
|
||||
|
@ -1044,11 +996,6 @@ namespace Moonlight.App.Database.Migrations
|
|||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b =>
|
||||
{
|
||||
b.Navigation("Limits");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
|
11
Moonlight/App/Helpers/FieldCssHelper.cs
Normal file
11
Moonlight/App/Helpers/FieldCssHelper.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class FieldCssHelper : FieldCssClassProvider
|
||||
{
|
||||
public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
|
||||
{
|
||||
return editContext.GetValidationMessages(fieldIdentifier).Any() ? "is-invalid" : "is-valid";
|
||||
}
|
||||
}
|
8
Moonlight/App/Helpers/Files/ContextAction.cs
Normal file
8
Moonlight/App/Helpers/Files/ContextAction.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Helpers.Files;
|
||||
|
||||
public class ContextAction
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public Action<FileData> Action { get; set; }
|
||||
}
|
24
Moonlight/App/Helpers/Files/FileAccess.cs
Normal file
24
Moonlight/App/Helpers/Files/FileAccess.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
namespace Moonlight.App.Helpers.Files;
|
||||
|
||||
public abstract class FileAccess : ICloneable
|
||||
{
|
||||
public string CurrentPath { get; set; } = "/";
|
||||
|
||||
public abstract Task<FileData[]> Ls();
|
||||
public abstract Task Cd(string dir);
|
||||
public abstract Task Up();
|
||||
public abstract Task SetDir(string dir);
|
||||
public abstract Task<string> Read(FileData fileData);
|
||||
public abstract Task Write(FileData fileData, string content);
|
||||
public abstract Task Upload(string name, Stream stream, Action<int>? progressUpdated = null);
|
||||
public abstract Task MkDir(string name);
|
||||
public abstract Task<string> Pwd();
|
||||
public abstract Task<string> DownloadUrl(FileData fileData);
|
||||
public abstract Task<Stream> DownloadStream(FileData fileData);
|
||||
public abstract Task Delete(FileData fileData);
|
||||
public abstract Task Move(FileData fileData, string newPath);
|
||||
public abstract Task Compress(params FileData[] files);
|
||||
public abstract Task Decompress(FileData fileData);
|
||||
public abstract Task<string> GetLaunchUrl();
|
||||
public abstract object Clone();
|
||||
}
|
8
Moonlight/App/Helpers/Files/FileData.cs
Normal file
8
Moonlight/App/Helpers/Files/FileData.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Helpers.Files;
|
||||
|
||||
public class FileData
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public long Size { get; set; }
|
||||
public bool IsFile { get; set; }
|
||||
}
|
232
Moonlight/App/Helpers/Files/WingsFileAccess.cs
Normal file
232
Moonlight/App/Helpers/Files/WingsFileAccess.cs
Normal file
|
@ -0,0 +1,232 @@
|
|||
using System.Web;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Models.Wings.Requests;
|
||||
using Moonlight.App.Models.Wings.Resources;
|
||||
using Moonlight.App.Services;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.Helpers.Files;
|
||||
|
||||
public class WingsFileAccess : FileAccess
|
||||
{
|
||||
private readonly WingsApiHelper WingsApiHelper;
|
||||
private readonly WingsJwtHelper WingsJwtHelper;
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly Server Server;
|
||||
private readonly User User;
|
||||
|
||||
public WingsFileAccess(
|
||||
WingsApiHelper wingsApiHelper,
|
||||
WingsJwtHelper wingsJwtHelper,
|
||||
Server server,
|
||||
ConfigService configService,
|
||||
User user)
|
||||
{
|
||||
WingsApiHelper = wingsApiHelper;
|
||||
WingsJwtHelper = wingsJwtHelper;
|
||||
Server = server;
|
||||
ConfigService = configService;
|
||||
User = user;
|
||||
|
||||
if (server.Node == null)
|
||||
{
|
||||
throw new ArgumentException("The wings file access server model needs to include the node data");
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<FileData[]> Ls()
|
||||
{
|
||||
var res = await WingsApiHelper.Get<ListDirectory[]>(
|
||||
Server.Node,
|
||||
$"api/servers/{Server.Uuid}/files/list-directory?directory={CurrentPath}"
|
||||
);
|
||||
|
||||
var x = new List<FileData>();
|
||||
|
||||
foreach (var response in res)
|
||||
{
|
||||
x.Add(new()
|
||||
{
|
||||
Name = response.Name,
|
||||
Size = response.File ? response.Size : 0,
|
||||
IsFile = response.File,
|
||||
});
|
||||
}
|
||||
|
||||
return x.ToArray();
|
||||
}
|
||||
|
||||
public override Task Cd(string dir)
|
||||
{
|
||||
var x = Path.Combine(CurrentPath, dir).Replace("\\", "/") + "/";
|
||||
x = x.Replace("//", "/");
|
||||
CurrentPath = x;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task Up()
|
||||
{
|
||||
CurrentPath = Path.GetFullPath(Path.Combine(CurrentPath, "..")).Replace("\\", "/").Replace("C:", "");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task SetDir(string dir)
|
||||
{
|
||||
CurrentPath = dir;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task<string> Read(FileData fileData)
|
||||
{
|
||||
return await WingsApiHelper.GetRaw(Server.Node,
|
||||
$"api/servers/{Server.Uuid}/files/contents?file={CurrentPath}{fileData.Name}");
|
||||
}
|
||||
|
||||
public override async Task Write(FileData fileData, string content)
|
||||
{
|
||||
await WingsApiHelper.PostRaw(Server.Node,
|
||||
$"api/servers/{Server.Uuid}/files/write?file={CurrentPath}{fileData.Name}", content);
|
||||
}
|
||||
|
||||
public override async Task Upload(string name, Stream dataStream, Action<int>? progressUpdated = null)
|
||||
{
|
||||
var token = WingsJwtHelper.Generate(
|
||||
Server.Node.Token,
|
||||
claims => { claims.Add("server_uuid", Server.Uuid.ToString()); }
|
||||
);
|
||||
|
||||
var client = new RestClient();
|
||||
var request = new RestRequest();
|
||||
|
||||
if (Server.Node.Ssl)
|
||||
request.Resource =
|
||||
$"https://{Server.Node.Fqdn}:{Server.Node.HttpPort}/upload/file?token={token}&directory={CurrentPath}";
|
||||
else
|
||||
request.Resource =
|
||||
$"http://{Server.Node.Fqdn}:{Server.Node.HttpPort}/upload/file?token={token}&directory={CurrentPath}";
|
||||
|
||||
request.AddParameter("name", "files");
|
||||
request.AddParameter("filename", name);
|
||||
request.AddHeader("Content-Type", "multipart/form-data");
|
||||
request.AddHeader("Origin", ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl"));
|
||||
request.AddFile("files", () =>
|
||||
{
|
||||
return new StreamProgressHelper(dataStream)
|
||||
{
|
||||
Progress = i => { progressUpdated?.Invoke(i); }
|
||||
};
|
||||
}, name);
|
||||
|
||||
await client.ExecutePostAsync(request);
|
||||
|
||||
client.Dispose();
|
||||
dataStream.Close();
|
||||
}
|
||||
|
||||
public override async Task MkDir(string name)
|
||||
{
|
||||
await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/create-directory",
|
||||
new CreateDirectory()
|
||||
{
|
||||
Name = name,
|
||||
Path = CurrentPath
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public override Task<string> Pwd()
|
||||
{
|
||||
return Task.FromResult(CurrentPath);
|
||||
}
|
||||
|
||||
public override Task<string> DownloadUrl(FileData fileData)
|
||||
{
|
||||
var token = WingsJwtHelper.Generate(Server.Node.Token, claims =>
|
||||
{
|
||||
claims.Add("server_uuid", Server.Uuid.ToString());
|
||||
claims.Add("file_path", CurrentPath + "/" + fileData.Name);
|
||||
});
|
||||
|
||||
if (Server.Node.Ssl)
|
||||
{
|
||||
return Task.FromResult(
|
||||
$"https://{Server.Node.Fqdn}:{Server.Node.HttpPort}/download/file?token={token}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(
|
||||
$"http://{Server.Node.Fqdn}:{Server.Node.HttpPort}/download/file?token={token}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<Stream> DownloadStream(FileData fileData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override async Task Delete(FileData fileData)
|
||||
{
|
||||
await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/delete", new DeleteFiles()
|
||||
{
|
||||
Root = CurrentPath,
|
||||
Files = new()
|
||||
{
|
||||
fileData.Name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override async Task Move(FileData fileData, string newPath)
|
||||
{
|
||||
var req = new RenameFiles()
|
||||
{
|
||||
Root = "/",
|
||||
Files = new[]
|
||||
{
|
||||
new RenameFilesData()
|
||||
{
|
||||
From = (CurrentPath + fileData.Name),
|
||||
To = newPath
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await WingsApiHelper.Put(Server.Node, $"api/servers/{Server.Uuid}/files/rename", req);
|
||||
}
|
||||
|
||||
public override async Task Compress(params FileData[] files)
|
||||
{
|
||||
var req = new CompressFiles()
|
||||
{
|
||||
Root = CurrentPath,
|
||||
Files = files.Select(x => x.Name).ToArray()
|
||||
};
|
||||
|
||||
await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/compress", req);
|
||||
}
|
||||
|
||||
public override async Task Decompress(FileData fileData)
|
||||
{
|
||||
var req = new DecompressFile()
|
||||
{
|
||||
Root = CurrentPath,
|
||||
File = fileData.Name
|
||||
};
|
||||
|
||||
await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/decompress", req);
|
||||
}
|
||||
|
||||
public override Task<string> GetLaunchUrl()
|
||||
{
|
||||
return Task.FromResult(
|
||||
$"sftp://{User.Id}.{StringHelper.IntToStringWithLeadingZeros(Server.Id, 8)}@{Server.Node.Fqdn}:{Server.Node.SftpPort}");
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new WingsFileAccess(WingsApiHelper, WingsJwtHelper, Server, ConfigService, User);
|
||||
}
|
||||
}
|
|
@ -24,11 +24,11 @@ public class PaperApiHelper
|
|||
else
|
||||
requrl = ApiUrl + "/" + url;
|
||||
|
||||
RestRequest request = new(requrl);
|
||||
RestRequest request = new(requrl, Method.Get);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
||||
var response = await client.GetAsync(request);
|
||||
var response = await client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
|
|
@ -14,24 +14,13 @@ public class WingsApiHelper
|
|||
Client = new();
|
||||
}
|
||||
|
||||
private string GetApiUrl(Node node)
|
||||
{
|
||||
if(node.Ssl)
|
||||
return $"https://{node.Fqdn}:{node.HttpPort}/";
|
||||
else
|
||||
return $"http://{node.Fqdn}:{node.HttpPort}/";
|
||||
//return $"https://{node.Fqdn}:{node.HttpPort}/";
|
||||
}
|
||||
|
||||
public async Task<T> Get<T>(Node node, string resource)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
var request = CreateRequest(node, resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
request.Method = Method.Get;
|
||||
|
||||
var response = await Client.GetAsync(request);
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
@ -53,13 +42,11 @@ public class WingsApiHelper
|
|||
|
||||
public async Task<string> GetRaw(Node node, string resource)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
var request = CreateRequest(node, resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
request.Method = Method.Get;
|
||||
|
||||
var response = await Client.GetAsync(request);
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
@ -81,18 +68,16 @@ public class WingsApiHelper
|
|||
|
||||
public async Task<T> Post<T>(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
var request = CreateRequest(node, resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
request.Method = Method.Post;
|
||||
|
||||
request.AddParameter("text/plain",
|
||||
JsonConvert.SerializeObject(body),
|
||||
ParameterType.RequestBody
|
||||
);
|
||||
|
||||
var response = await Client.PostAsync(request);
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
@ -114,16 +99,14 @@ public class WingsApiHelper
|
|||
|
||||
public async Task Post(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
var request = CreateRequest(node, resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
request.Method = Method.Post;
|
||||
|
||||
if(body != null)
|
||||
if(body != null)
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.PostAsync(request);
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
@ -143,15 +126,13 @@ public class WingsApiHelper
|
|||
|
||||
public async Task PostRaw(Node node, string resource, object body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
var request = CreateRequest(node, resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
request.Method = Method.Post;
|
||||
|
||||
request.AddParameter("text/plain", body, ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.PostAsync(request);
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
@ -171,16 +152,14 @@ public class WingsApiHelper
|
|||
|
||||
public async Task Delete(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
var request = CreateRequest(node, resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
request.Method = Method.Delete;
|
||||
|
||||
if(body != null)
|
||||
if(body != null)
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.DeleteAsync(request);
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
@ -200,15 +179,13 @@ public class WingsApiHelper
|
|||
|
||||
public async Task Put(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
var request = CreateRequest(node, resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
request.Method = Method.Put;
|
||||
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.PutAsync(request);
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
|
@ -225,4 +202,20 @@ public class WingsApiHelper
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RestRequest CreateRequest(Node node, string resource)
|
||||
{
|
||||
var url = (node.Ssl ? "https" : "http") + $"://{node.Fqdn}:{node.HttpPort}/" + resource;
|
||||
|
||||
var request = new RestRequest(url)
|
||||
{
|
||||
Timeout = 60 * 15
|
||||
};
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
|
@ -80,6 +80,13 @@ public class WingsServerConverter
|
|||
wingsServer.Settings.Environment.Add(v.Key, v.Value);
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (var allocation in server.Allocations)
|
||||
{
|
||||
wingsServer.Settings.Environment.Add("ML_PORT_" + i, allocation.Port.ToString());
|
||||
i++;
|
||||
}
|
||||
|
||||
// Stop
|
||||
if (image.StopCommand.StartsWith("!"))
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/moonlight/payments")]
|
||||
public class PaymentsController : Controller
|
||||
{
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly SubscriptionService SubscriptionService;
|
||||
|
||||
public PaymentsController(ConfigService configService, SubscriptionService subscriptionService)
|
||||
{
|
||||
ConfigService = configService;
|
||||
SubscriptionService = subscriptionService;
|
||||
}
|
||||
|
||||
[HttpGet("generate")]
|
||||
public async Task<ActionResult> GenerateGet([FromQuery] string key, [FromQuery] int subscriptionId)
|
||||
{
|
||||
var validKey = ConfigService
|
||||
.GetSection("Moonlight")
|
||||
.GetSection("Payments")
|
||||
.GetValue<string>("Key");
|
||||
|
||||
if (key != validKey)
|
||||
return StatusCode(403);
|
||||
|
||||
var token = await SubscriptionService.ProcessGenerate(subscriptionId);
|
||||
|
||||
return Ok(token);
|
||||
}
|
||||
|
||||
[HttpPost("generate")]
|
||||
public async Task<ActionResult> GeneratePost([FromQuery] string key, [FromQuery] int subscriptionId)
|
||||
{
|
||||
var validKey = ConfigService
|
||||
.GetSection("Moonlight")
|
||||
.GetSection("Payments")
|
||||
.GetValue<string>("Key");
|
||||
|
||||
if (key != validKey)
|
||||
return StatusCode(403);
|
||||
|
||||
var token = await SubscriptionService.ProcessGenerate(subscriptionId);
|
||||
|
||||
return Ok(token);
|
||||
}
|
||||
}
|
|
@ -21,7 +21,10 @@ public class ResourcesController : Controller
|
|||
{
|
||||
if (name.Contains(".."))
|
||||
{
|
||||
await SecurityLogService.Log(SecurityLogType.PathTransversal, name);
|
||||
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
|
||||
{
|
||||
x.Add<string>(name);
|
||||
});
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public class WingsFileAccess : IFileAccess
|
|||
|
||||
public async Task<FileManagerObject[]> GetDirectoryContent()
|
||||
{
|
||||
var res = await WingsApiHelper.Get<ListDirectoryRequest[]>(Node,
|
||||
var res = await WingsApiHelper.Get<ListDirectory[]>(Node,
|
||||
$"api/servers/{Server.Uuid}/files/list-directory?directory={Path}");
|
||||
|
||||
var x = new List<FileManagerObject>();
|
||||
|
@ -130,7 +130,7 @@ public class WingsFileAccess : IFileAccess
|
|||
public async Task CreateDirectory(string name)
|
||||
{
|
||||
await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/create-directory",
|
||||
new CreateDirectoryRequest()
|
||||
new CreateDirectory()
|
||||
{
|
||||
Name = name,
|
||||
Path = Path
|
||||
|
@ -171,7 +171,7 @@ public class WingsFileAccess : IFileAccess
|
|||
|
||||
public async Task Delete(FileManagerObject managerObject)
|
||||
{
|
||||
await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/delete", new DeleteFilesRequest()
|
||||
await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/delete", new DeleteFiles()
|
||||
{
|
||||
Root = Path,
|
||||
Files = new()
|
||||
|
@ -183,7 +183,7 @@ public class WingsFileAccess : IFileAccess
|
|||
|
||||
public async Task Move(FileManagerObject managerObject, string newPath)
|
||||
{
|
||||
await WingsApiHelper.Put(Node, $"api/servers/{Server.Uuid}/files/rename", new RenameFilesRequest()
|
||||
await WingsApiHelper.Put(Node, $"api/servers/{Server.Uuid}/files/rename", new RenameFiles()
|
||||
{
|
||||
Root = "/",
|
||||
Files = new[]
|
||||
|
|
8
Moonlight/App/Models/Files/FileContextAction.cs
Normal file
8
Moonlight/App/Models/Files/FileContextAction.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Models.Files;
|
||||
|
||||
public class FileContextAction
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Action<FileManagerObject> Action { get; set; }
|
||||
}
|
14
Moonlight/App/Models/Forms/LoginDataModel.cs
Normal file
14
Moonlight/App/Models/Forms/LoginDataModel.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
|
||||
public class LoginDataModel
|
||||
{
|
||||
[Required(ErrorMessage = "You need to enter an email address")]
|
||||
[EmailAddress(ErrorMessage = "You need to enter a valid email address")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "You need to enter a password")]
|
||||
[MinLength(8, ErrorMessage = "You need to enter a password with minimum 8 characters in lenght")]
|
||||
public string Password { get; set; }
|
||||
}
|
9
Moonlight/App/Models/Forms/LoginTotpDataModel.cs
Normal file
9
Moonlight/App/Models/Forms/LoginTotpDataModel.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
|
||||
public class LoginTotpDataModel
|
||||
{
|
||||
[Required(ErrorMessage = "You need to enter a 2fa code")]
|
||||
public string Code { get; set; } = "";
|
||||
}
|
14
Moonlight/App/Models/Forms/NameModel.cs
Normal file
14
Moonlight/App/Models/Forms/NameModel.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
|
||||
public class NameModel
|
||||
{
|
||||
[Required]
|
||||
[MinLength(2, ErrorMessage = "Do you think, that works?")]
|
||||
public string FirstName { get; set; }
|
||||
|
||||
[Required]
|
||||
[MinLength(2, ErrorMessage = "Do you think, that works?")]
|
||||
public string LastName { get; set; }
|
||||
}
|
10
Moonlight/App/Models/Forms/PasswordModel.cs
Normal file
10
Moonlight/App/Models/Forms/PasswordModel.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
|
||||
public class PasswordModel
|
||||
{
|
||||
[Required(ErrorMessage = "You need to enter a password")]
|
||||
[MinLength(8, ErrorMessage = "You need to enter a password with minimum 8 characters in lenght")]
|
||||
public string Password { get; set; }
|
||||
}
|
13
Moonlight/App/Models/Forms/SubscriptionDataModel.cs
Normal file
13
Moonlight/App/Models/Forms/SubscriptionDataModel.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
|
||||
public class SubscriptionDataModel
|
||||
{
|
||||
[Required(ErrorMessage = "You need to enter a name")]
|
||||
[MaxLength(32, ErrorMessage = "Max lenght for name is 32")]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "You need to enter a description")]
|
||||
public string Description { get; set; } = "";
|
||||
}
|
7
Moonlight/App/Models/Log/LogData.cs
Normal file
7
Moonlight/App/Models/Log/LogData.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Moonlight.App.Models.Log;
|
||||
|
||||
public class LogData
|
||||
{
|
||||
public Type Type { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
|
@ -19,5 +19,9 @@ public enum AuditLogType
|
|||
AddDomainRecord,
|
||||
UpdateDomainRecord,
|
||||
DeleteDomainRecord,
|
||||
PasswordReset
|
||||
PasswordReset,
|
||||
CleanupEnabled,
|
||||
CleanupDisabled,
|
||||
CleanupTriggered,
|
||||
PasswordChange,
|
||||
}
|
14
Moonlight/App/Models/Misc/SubscriptionLimit.cs
Normal file
14
Moonlight/App/Models/Misc/SubscriptionLimit.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace Moonlight.App.Models.Misc;
|
||||
|
||||
public class SubscriptionLimit
|
||||
{
|
||||
public string Identifier { get; set; } = "";
|
||||
public int Amount { get; set; }
|
||||
public List<LimitOption> Options { get; set; } = new();
|
||||
|
||||
public class LimitOption
|
||||
{
|
||||
public string Key { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
}
|
||||
}
|
|
@ -9,5 +9,6 @@ public enum UserStatus
|
|||
Warned,
|
||||
Banned,
|
||||
Disabled,
|
||||
DataPending
|
||||
DataPending,
|
||||
PasswordPending
|
||||
}
|
12
Moonlight/App/Models/Wings/Requests/CompressFiles.cs
Normal file
12
Moonlight/App/Models/Wings/Requests/CompressFiles.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class CompressFiles
|
||||
{
|
||||
[JsonProperty("root")]
|
||||
public string Root { get; set; }
|
||||
|
||||
[JsonProperty("files")]
|
||||
public string[] Files { get; set; }
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class CreateBackupRequest
|
||||
public class CreateBackup
|
||||
{
|
||||
[JsonProperty("adapter")]
|
||||
public string Adapter { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class CreateDirectoryRequest
|
||||
public class CreateDirectory
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class CreateServerRequest
|
||||
public class CreateServer
|
||||
{
|
||||
[JsonProperty("uuid")]
|
||||
public Guid Uuid { get; set; }
|
12
Moonlight/App/Models/Wings/Requests/DecompressFile.cs
Normal file
12
Moonlight/App/Models/Wings/Requests/DecompressFile.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class DecompressFile
|
||||
{
|
||||
[JsonProperty("root")]
|
||||
public string Root { get; set; }
|
||||
|
||||
[JsonProperty("file")]
|
||||
public string File { get; set; }
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class DeleteFilesRequest
|
||||
public class DeleteFiles
|
||||
{
|
||||
[JsonProperty("root")]
|
||||
public string Root { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class RenameFilesRequest
|
||||
public class RenameFiles
|
||||
{
|
||||
[JsonProperty("root")]
|
||||
public string Root { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class RestoreBackupRequest
|
||||
public class RestoreBackup
|
||||
{
|
||||
[JsonProperty("adapter")]
|
||||
public string Adapter { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class ServerPowerRequest
|
||||
public class ServerPower
|
||||
{
|
||||
[JsonProperty("action")]
|
||||
public string Action { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Resources;
|
||||
|
||||
public class ListDirectoryRequest
|
||||
public class ListDirectory
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Moonlight.App.Models.Wings.Resources;
|
||||
|
||||
public class ServerDetailsResponse
|
||||
public class ServerDetails
|
||||
{
|
||||
[JsonProperty("state")]
|
||||
public string State { get; set; }
|
||||
|
@ -11,9 +11,9 @@ public class ServerDetailsResponse
|
|||
public bool IsSuspended { get; set; }
|
||||
|
||||
[JsonProperty("utilization")]
|
||||
public ServerDetailsResponseUtilization Utilization { get; set; }
|
||||
public ServerDetailsUtilization Utilization { get; set; }
|
||||
|
||||
public class ServerDetailsResponseUtilization
|
||||
public class ServerDetailsUtilization
|
||||
{
|
||||
[JsonProperty("memory_bytes")]
|
||||
public long MemoryBytes { get; set; }
|
||||
|
@ -25,7 +25,7 @@ public class ServerDetailsResponse
|
|||
public double CpuAbsolute { get; set; }
|
||||
|
||||
[JsonProperty("network")]
|
||||
public ServerDetailsResponseNetwork Network { get; set; }
|
||||
public ServerDetailsNetwork Network { get; set; }
|
||||
|
||||
[JsonProperty("uptime")]
|
||||
public long Uptime { get; set; }
|
||||
|
@ -37,7 +37,7 @@ public class ServerDetailsResponse
|
|||
public long DiskBytes { get; set; }
|
||||
}
|
||||
|
||||
public class ServerDetailsResponseNetwork
|
||||
public class ServerDetailsNetwork
|
||||
{
|
||||
[JsonProperty("rx_bytes")]
|
||||
public long RxBytes { get; set; }
|
|
@ -2,7 +2,7 @@
|
|||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Repositories.Subscriptions;
|
||||
namespace Moonlight.App.Repositories;
|
||||
|
||||
public class SubscriptionRepository : IDisposable
|
||||
{
|
|
@ -1,44 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Repositories.Subscriptions;
|
||||
|
||||
public class SubscriptionLimitRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public SubscriptionLimitRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public DbSet<SubscriptionLimit> Get()
|
||||
{
|
||||
return DataContext.SubscriptionLimits;
|
||||
}
|
||||
|
||||
public SubscriptionLimit Add(SubscriptionLimit subscription)
|
||||
{
|
||||
var x = DataContext.SubscriptionLimits.Add(subscription);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public void Update(SubscriptionLimit subscription)
|
||||
{
|
||||
DataContext.SubscriptionLimits.Update(subscription);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(SubscriptionLimit subscription)
|
||||
{
|
||||
DataContext.SubscriptionLimits.Remove(subscription);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
216
Moonlight/App/Services/CleanupService.cs
Normal file
216
Moonlight/App/Services/CleanupService.cs
Normal file
|
@ -0,0 +1,216 @@
|
|||
using System.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MineStatLib;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Models.Daemon.Resources;
|
||||
using Moonlight.App.Models.Wings;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
using Logging.Net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class CleanupService
|
||||
{
|
||||
#region Stats
|
||||
public DateTime StartedAt { get; private set; }
|
||||
public DateTime CompletedAt { get; private set; }
|
||||
public int ServersCleaned { get; private set; }
|
||||
public int CleanupsPerformed { get; private set; }
|
||||
public int ServersRunning { get; private set; }
|
||||
public bool IsRunning { get; private set; }
|
||||
#endregion
|
||||
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly MessageService MessageService;
|
||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||
private readonly PeriodicTimer Timer;
|
||||
|
||||
public CleanupService(
|
||||
ConfigService configService,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
MessageService messageService)
|
||||
{
|
||||
ServiceScopeFactory = serviceScopeFactory;
|
||||
MessageService = messageService;
|
||||
ConfigService = configService;
|
||||
|
||||
StartedAt = DateTime.Now;
|
||||
CompletedAt = DateTime.Now;
|
||||
IsRunning = false;
|
||||
|
||||
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
||||
|
||||
if (!config.GetValue<bool>("Enable") || ConfigService.DebugMode)
|
||||
{
|
||||
Logger.Info("Disabling cleanup service");
|
||||
return;
|
||||
}
|
||||
|
||||
Timer = new(TimeSpan.FromMinutes(config.GetValue<int>("Wait")));
|
||||
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
private async Task Run()
|
||||
{
|
||||
while (await Timer.WaitForNextTickAsync())
|
||||
{
|
||||
IsRunning = true;
|
||||
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
||||
|
||||
var maxCpu = config.GetValue<int>("Cpu");
|
||||
var minMemory = config.GetValue<int>("Memory");
|
||||
var maxUptime = config.GetValue<int>("Uptime");
|
||||
var minUptime = config.GetValue<int>("MinUptime");
|
||||
|
||||
var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>();
|
||||
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
||||
|
||||
var nodes = nodeRepository
|
||||
.Get()
|
||||
.ToArray();
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cpuStats = await nodeService.GetCpuStats(node);
|
||||
var memoryStats = await nodeService.GetMemoryStats(node);
|
||||
|
||||
if (cpuStats.Usage > maxCpu || memoryStats.Free < minMemory)
|
||||
{
|
||||
var containerStats = await nodeService.GetContainerStats(node);
|
||||
|
||||
var serverRepository = scope.ServiceProvider.GetRequiredService<ServerRepository>();
|
||||
var imageRepository = scope.ServiceProvider.GetRequiredService<ImageRepository>();
|
||||
|
||||
var images = imageRepository
|
||||
.Get()
|
||||
.ToArray();
|
||||
|
||||
var imagesWithFlag = images
|
||||
.Where(x =>
|
||||
(JsonConvert.DeserializeObject<string[]>(x.TagsJson) ?? Array.Empty<string>()).Contains("cleanup")
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
var containerMappedToServers = new Dictionary<ContainerStats.Container, Server>();
|
||||
|
||||
foreach (var container in containerStats.Containers)
|
||||
{
|
||||
if (Guid.TryParse(container.Name, out Guid uuid))
|
||||
{
|
||||
var server = serverRepository
|
||||
.Get()
|
||||
.Include(x => x.Image)
|
||||
.Include(x => x.MainAllocation)
|
||||
.Include(x => x.Variables)
|
||||
.FirstOrDefault(x => x.Uuid == uuid);
|
||||
|
||||
if (server != null && imagesWithFlag.Any(y => y.Id == server.Image.Id))
|
||||
{
|
||||
containerMappedToServers.Add(container, server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
||||
|
||||
foreach (var containerMapped in containerMappedToServers)
|
||||
{
|
||||
var server = containerMapped.Value;
|
||||
|
||||
try
|
||||
{
|
||||
var stats = await serverService.GetDetails(server);
|
||||
|
||||
if (server.IsCleanupException)
|
||||
{
|
||||
if (stats.Utilization.Uptime > TimeSpan.FromHours(maxUptime).TotalMilliseconds)
|
||||
{
|
||||
var players = GetPlayers(node, server.MainAllocation);
|
||||
|
||||
if (players == 0)
|
||||
{
|
||||
await serverService.SetPowerState(server, PowerSignal.Restart);
|
||||
|
||||
ServersCleaned++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServersRunning++;
|
||||
}
|
||||
|
||||
await MessageService.Emit("cleanup.updated", null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stats.Utilization.Uptime > TimeSpan.FromMinutes(minUptime).TotalMilliseconds)
|
||||
{
|
||||
var players = GetPlayers(node, server.MainAllocation);
|
||||
|
||||
if (players < 1)
|
||||
{
|
||||
var j2SVar = server.Variables.FirstOrDefault(x => x.Key == "J2S");
|
||||
var handleJ2S = j2SVar != null && j2SVar.Value == "1";
|
||||
|
||||
if (handleJ2S)
|
||||
{
|
||||
await serverService.SetPowerState(server, PowerSignal.Restart);
|
||||
}
|
||||
else
|
||||
{
|
||||
await serverService.SetPowerState(server, PowerSignal.Stop);
|
||||
}
|
||||
|
||||
ServersCleaned++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServersRunning++;
|
||||
}
|
||||
|
||||
await MessageService.Emit("cleanup.updated", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn($"Error checking server {server.Name} ({server.Id})");
|
||||
Logger.Warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Error performing cleanup on node {node.Name} ({node.Id})");
|
||||
Logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
IsRunning = false;
|
||||
CleanupsPerformed++;
|
||||
await MessageService.Emit("cleanup.updated", null);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetPlayers(Node node, NodeAllocation allocation)
|
||||
{
|
||||
var ms = new MineStat(node.Fqdn, (ushort)allocation.Port);
|
||||
|
||||
//TODO: Add fake player check
|
||||
|
||||
if (ms.ServerUp)
|
||||
{
|
||||
return ms.CurrentPlayersInt;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Text;
|
||||
using Logging.Net;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moonlight.App.Helpers;
|
||||
|
||||
|
@ -21,6 +22,9 @@ public class ConfigService : IConfiguration
|
|||
|
||||
if (debugVar != null)
|
||||
DebugMode = bool.Parse(debugVar);
|
||||
|
||||
if(DebugMode)
|
||||
Logger.Debug("Debug mode enabled");
|
||||
}
|
||||
|
||||
public IEnumerable<IConfigurationSection> GetChildren()
|
||||
|
|
|
@ -169,7 +169,11 @@ public class DomainService
|
|||
}));
|
||||
}
|
||||
|
||||
await AuditLogService.Log(AuditLogType.AddDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
|
||||
await AuditLogService.Log(AuditLogType.AddDomainRecord, x =>
|
||||
{
|
||||
x.Add<Domain>(d.Id);
|
||||
x.Add<DnsRecord>(dnsRecord.Name);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||
|
@ -199,7 +203,11 @@ public class DomainService
|
|||
}));
|
||||
}
|
||||
|
||||
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
|
||||
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, x =>
|
||||
{
|
||||
x.Add<Domain>(d.Id);
|
||||
x.Add<DnsRecord>(dnsRecord.Name);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||
|
@ -210,7 +218,11 @@ public class DomainService
|
|||
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
|
||||
);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
|
||||
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, x =>
|
||||
{
|
||||
x.Add<Domain>(d.Id);
|
||||
x.Add<DnsRecord>(dnsRecord.Name);
|
||||
});
|
||||
}
|
||||
|
||||
private Domain EnsureData(Domain domain)
|
||||
|
|
23
Moonlight/App/Services/Interop/ModalService.cs
Normal file
23
Moonlight/App/Services/Interop/ModalService.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Moonlight.App.Services.Interop;
|
||||
|
||||
public class ModalService
|
||||
{
|
||||
private readonly IJSRuntime JsRuntime;
|
||||
|
||||
public ModalService(IJSRuntime jsRuntime)
|
||||
{
|
||||
JsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task Show(string name)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.modals.show", name);
|
||||
}
|
||||
|
||||
public async Task Hide(string name)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.modals.hide", name);
|
||||
}
|
||||
}
|
|
@ -30,4 +30,19 @@ public class ToastService
|
|||
{
|
||||
await JsRuntime.InvokeVoidAsync("showSuccessToast", message);
|
||||
}
|
||||
|
||||
public async Task CreateProcessToast(string id, string text)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("createToast", id, text);
|
||||
}
|
||||
|
||||
public async Task UpdateProcessToast(string id, string text)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("modifyToast", id, text);
|
||||
}
|
||||
|
||||
public async Task RemoveProcessToast(string id)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("removeToast", id);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
using Moonlight.App.Models.Log;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories.LogEntries;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
@ -19,16 +20,18 @@ public class AuditLogService
|
|||
HttpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public Task Log(AuditLogType type, params object[] data)
|
||||
public Task Log(AuditLogType type, Action<AuditLogParameters> data)
|
||||
{
|
||||
var ip = GetIp();
|
||||
var al = new AuditLogParameters();
|
||||
data(al);
|
||||
|
||||
var entry = new AuditLogEntry()
|
||||
{
|
||||
Ip = ip,
|
||||
Type = type,
|
||||
System = false,
|
||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
||||
JsonData = al.Build()
|
||||
};
|
||||
|
||||
Repository.Add(entry);
|
||||
|
@ -36,13 +39,16 @@ public class AuditLogService
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task LogSystem(AuditLogType type, params object[] data)
|
||||
public Task LogSystem(AuditLogType type, Action<AuditLogParameters> data)
|
||||
{
|
||||
var al = new AuditLogParameters();
|
||||
data(al);
|
||||
|
||||
var entry = new AuditLogEntry()
|
||||
{
|
||||
Type = type,
|
||||
System = true,
|
||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
||||
JsonData = al.Build()
|
||||
};
|
||||
|
||||
Repository.Add(entry);
|
||||
|
@ -62,4 +68,23 @@ public class AuditLogService
|
|||
|
||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||
}
|
||||
|
||||
public class AuditLogParameters
|
||||
{
|
||||
private List<LogData> Data = new List<LogData>();
|
||||
|
||||
public void Add<T>(object data)
|
||||
{
|
||||
Data.Add(new LogData()
|
||||
{
|
||||
Type = typeof(T),
|
||||
Value = data.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
internal string Build()
|
||||
{
|
||||
return JsonConvert.SerializeObject(Data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
using Moonlight.App.Models.Log;
|
||||
using Moonlight.App.Repositories.LogEntries;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -18,15 +19,17 @@ public class ErrorLogService
|
|||
HttpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public Task Log(Exception exception, params object[] objects)
|
||||
public Task Log(Exception exception, Action<ErrorLogParameters> data)
|
||||
{
|
||||
var ip = GetIp();
|
||||
var al = new ErrorLogParameters();
|
||||
data(al);
|
||||
|
||||
var entry = new ErrorLogEntry()
|
||||
{
|
||||
Ip = ip,
|
||||
System = false,
|
||||
JsonData = !objects.Any() ? "" : JsonConvert.SerializeObject(objects),
|
||||
JsonData = al.Build(),
|
||||
Class = NameOfCallingClass(),
|
||||
Stacktrace = exception.ToStringDemystified()
|
||||
};
|
||||
|
@ -36,12 +39,15 @@ public class ErrorLogService
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task LogSystem(Exception exception, params object[] objects)
|
||||
public Task LogSystem(Exception exception, Action<ErrorLogParameters> data)
|
||||
{
|
||||
var al = new ErrorLogParameters();
|
||||
data(al);
|
||||
|
||||
var entry = new ErrorLogEntry()
|
||||
{
|
||||
System = true,
|
||||
JsonData = !objects.Any() ? "" : JsonConvert.SerializeObject(objects),
|
||||
JsonData = al.Build(),
|
||||
Class = NameOfCallingClass(),
|
||||
Stacktrace = exception.ToStringDemystified()
|
||||
};
|
||||
|
@ -87,4 +93,23 @@ public class ErrorLogService
|
|||
|
||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||
}
|
||||
|
||||
public class ErrorLogParameters
|
||||
{
|
||||
private List<LogData> Data = new List<LogData>();
|
||||
|
||||
public void Add<T>(object data)
|
||||
{
|
||||
Data.Add(new LogData()
|
||||
{
|
||||
Type = typeof(T),
|
||||
Value = data.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
internal string Build()
|
||||
{
|
||||
return JsonConvert.SerializeObject(Data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
using Moonlight.App.Models.Log;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories.LogEntries;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
@ -17,16 +18,18 @@ public class SecurityLogService
|
|||
HttpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public Task Log(SecurityLogType type, params object[] data)
|
||||
public Task Log(SecurityLogType type, Action<SecurityLogParameters> data)
|
||||
{
|
||||
var ip = GetIp();
|
||||
var al = new SecurityLogParameters();
|
||||
data(al);
|
||||
|
||||
var entry = new SecurityLogEntry()
|
||||
{
|
||||
Ip = ip,
|
||||
Type = type,
|
||||
System = false,
|
||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
||||
JsonData = al.Build()
|
||||
};
|
||||
|
||||
Repository.Add(entry);
|
||||
|
@ -34,13 +37,16 @@ public class SecurityLogService
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task LogSystem(SecurityLogType type, params object[] data)
|
||||
public Task LogSystem(SecurityLogType type, Action<SecurityLogParameters> data)
|
||||
{
|
||||
var al = new SecurityLogParameters();
|
||||
data(al);
|
||||
|
||||
var entry = new SecurityLogEntry()
|
||||
{
|
||||
Type = type,
|
||||
System = true,
|
||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
||||
JsonData = al.Build()
|
||||
};
|
||||
|
||||
Repository.Add(entry);
|
||||
|
@ -60,4 +66,24 @@ public class SecurityLogService
|
|||
|
||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||
}
|
||||
|
||||
|
||||
public class SecurityLogParameters
|
||||
{
|
||||
private List<LogData> Data = new List<LogData>();
|
||||
|
||||
public void Add<T>(object data)
|
||||
{
|
||||
Data.Add(new LogData()
|
||||
{
|
||||
Type = typeof(T),
|
||||
Value = data.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
internal string Build()
|
||||
{
|
||||
return JsonConvert.SerializeObject(Data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,7 +76,10 @@ public class OneTimeJwtService
|
|||
}
|
||||
catch (SignatureVerificationException)
|
||||
{
|
||||
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, token);
|
||||
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, x =>
|
||||
{
|
||||
x.Add<string>(token);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -75,11 +75,11 @@ public class ServerService
|
|||
return s;
|
||||
}
|
||||
|
||||
public async Task<ServerDetailsResponse> GetDetails(Server s)
|
||||
public async Task<ServerDetails> GetDetails(Server s)
|
||||
{
|
||||
Server server = EnsureNodeData(s);
|
||||
|
||||
return await WingsApiHelper.Get<ServerDetailsResponse>(
|
||||
return await WingsApiHelper.Get<ServerDetails>(
|
||||
server.Node,
|
||||
$"api/servers/{server.Uuid}"
|
||||
);
|
||||
|
@ -91,12 +91,16 @@ public class ServerService
|
|||
|
||||
var rawSignal = signal.ToString().ToLower();
|
||||
|
||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/power", new ServerPowerRequest()
|
||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/power", new ServerPower()
|
||||
{
|
||||
Action = rawSignal
|
||||
});
|
||||
|
||||
await AuditLogService.Log(AuditLogType.ChangePowerState, new[] { server.Uuid.ToString(), rawSignal });
|
||||
await AuditLogService.Log(AuditLogType.ChangePowerState, x =>
|
||||
{
|
||||
x.Add<Server>(server.Uuid);
|
||||
x.Add<PowerSignal>(rawSignal);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<ServerBackup> CreateBackup(Server server)
|
||||
|
@ -118,7 +122,7 @@ public class ServerService
|
|||
serverData.Backups.Add(backup);
|
||||
ServerRepository.Update(serverData);
|
||||
|
||||
await WingsApiHelper.Post(serverData.Node, $"api/servers/{serverData.Uuid}/backup", new CreateBackupRequest()
|
||||
await WingsApiHelper.Post(serverData.Node, $"api/servers/{serverData.Uuid}/backup", new CreateBackup()
|
||||
{
|
||||
Adapter = "wings",
|
||||
Uuid = backup.Uuid,
|
||||
|
@ -126,7 +130,11 @@ public class ServerService
|
|||
});
|
||||
|
||||
await AuditLogService.Log(AuditLogType.CreateBackup,
|
||||
new[] { serverData.Uuid.ToString(), backup.Uuid.ToString() });
|
||||
x =>
|
||||
{
|
||||
x.Add<Server>(server.Uuid);
|
||||
x.Add<ServerBackup>(backup.Uuid);
|
||||
});
|
||||
|
||||
return backup;
|
||||
}
|
||||
|
@ -158,13 +166,17 @@ public class ServerService
|
|||
Server server = EnsureNodeData(s);
|
||||
|
||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/backup/{serverBackup.Uuid}/restore",
|
||||
new RestoreBackupRequest()
|
||||
new RestoreBackup()
|
||||
{
|
||||
Adapter = "wings"
|
||||
});
|
||||
|
||||
await AuditLogService.Log(AuditLogType.RestoreBackup,
|
||||
new[] { s.Uuid.ToString(), serverBackup.Uuid.ToString() });
|
||||
x =>
|
||||
{
|
||||
x.Add<Server>(server.Uuid);
|
||||
x.Add<ServerBackup>(serverBackup.Uuid);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
|
||||
|
@ -186,7 +198,11 @@ public class ServerService
|
|||
await MessageService.Emit("wings.backups.delete", backup);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.DeleteBackup,
|
||||
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
|
||||
x =>
|
||||
{
|
||||
x.Add<Server>(server.Uuid);
|
||||
x.Add<ServerBackup>(backup.Uuid);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
|
||||
|
@ -200,7 +216,11 @@ public class ServerService
|
|||
});
|
||||
|
||||
await AuditLogService.Log(AuditLogType.DownloadBackup,
|
||||
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
|
||||
x =>
|
||||
{
|
||||
x.Add<Server>(server.Uuid);
|
||||
x.Add<ServerBackup>(serverBackup.Uuid);
|
||||
});
|
||||
|
||||
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
|
||||
}
|
||||
|
@ -299,19 +319,26 @@ public class ServerService
|
|||
|
||||
try
|
||||
{
|
||||
await WingsApiHelper.Post(node, $"api/servers", new CreateServerRequest()
|
||||
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
||||
{
|
||||
Uuid = newServerData.Uuid,
|
||||
StartOnCompletion = false
|
||||
});
|
||||
|
||||
await AuditLogService.Log(AuditLogType.CreateServer, newServerData.Uuid.ToString());
|
||||
await AuditLogService.Log(AuditLogType.CreateServer, x =>
|
||||
{
|
||||
x.Add<Server>(newServerData.Uuid);
|
||||
});
|
||||
|
||||
return newServerData;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorLogService.Log(e, new[] { newServerData.Uuid.ToString(), node.Id.ToString() });
|
||||
await ErrorLogService.Log(e, x =>
|
||||
{
|
||||
x.Add<Server>(newServerData.Uuid);
|
||||
x.Add<Node>(node.Id);
|
||||
});
|
||||
|
||||
ServerRepository.Delete(newServerData);
|
||||
|
||||
|
@ -325,7 +352,10 @@ public class ServerService
|
|||
|
||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.ReinstallServer, server.Uuid.ToString());
|
||||
await AuditLogService.Log(AuditLogType.ReinstallServer, x =>
|
||||
{
|
||||
x.Add<Server>(server.Uuid);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
|
||||
|
@ -334,7 +364,10 @@ public class ServerService
|
|||
|
||||
if (server == null)
|
||||
{
|
||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, serverId);
|
||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
||||
{
|
||||
x.Add<int>(id);
|
||||
});
|
||||
throw new Exception("Server not found");
|
||||
}
|
||||
|
||||
|
|
|
@ -89,12 +89,15 @@ public class IdentityService
|
|||
}
|
||||
catch (SignatureVerificationException)
|
||||
{
|
||||
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, token);
|
||||
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, x =>
|
||||
{
|
||||
x.Add<string>(token);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorLogService.Log(e);
|
||||
await ErrorLogService.Log(e, x => {});
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -130,7 +133,7 @@ public class IdentityService
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ErrorLogService.Log(e);
|
||||
await ErrorLogService.Log(e, x => {});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
45
Moonlight/App/Services/SubscriptionAdminService.cs
Normal file
45
Moonlight/App/Services/SubscriptionAdminService.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
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))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,126 +3,138 @@ using Moonlight.App.Database.Entities;
|
|||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Subscriptions;
|
||||
using Moonlight.App.Services.LogServices;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class SubscriptionService
|
||||
{
|
||||
private readonly SubscriptionRepository SubscriptionRepository;
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly OneTimeJwtService OneTimeJwtService;
|
||||
private readonly AuditLogService AuditLogService;
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly ConfigService ConfigService;
|
||||
|
||||
public SubscriptionService(SubscriptionRepository subscriptionRepository,
|
||||
UserRepository userRepository,
|
||||
IdentityService identityService,
|
||||
ConfigService configService,
|
||||
public SubscriptionService(
|
||||
SubscriptionRepository subscriptionRepository,
|
||||
OneTimeJwtService oneTimeJwtService,
|
||||
AuditLogService auditLogService)
|
||||
IdentityService identityService,
|
||||
UserRepository userRepository,
|
||||
ConfigService configService)
|
||||
{
|
||||
SubscriptionRepository = subscriptionRepository;
|
||||
UserRepository = userRepository;
|
||||
IdentityService = identityService;
|
||||
ConfigService = configService;
|
||||
OneTimeJwtService = oneTimeJwtService;
|
||||
AuditLogService = auditLogService;
|
||||
IdentityService = identityService;
|
||||
UserRepository = userRepository;
|
||||
ConfigService = configService;
|
||||
}
|
||||
|
||||
public async Task<Subscription?> Get()
|
||||
public async Task<Subscription?> GetCurrent()
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
var advancedUser = UserRepository
|
||||
.Get()
|
||||
.Include(x => x.Subscription)
|
||||
.First(x => x.Id == user!.Id);
|
||||
var user = await GetCurrentUser();
|
||||
|
||||
if (advancedUser.Subscription == null)
|
||||
if (user == null || user.CurrentSubscription == null)
|
||||
return null;
|
||||
|
||||
return SubscriptionRepository
|
||||
.Get()
|
||||
.Include(x => x.Limits)
|
||||
.Include("Limits.Image")
|
||||
.First(x => x.Id == advancedUser.Subscription.Id);
|
||||
}
|
||||
public async Task Cancel()
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
user!.Subscription = null;
|
||||
UserRepository.Update(user!);
|
||||
var subscriptionEnd = user.SubscriptionSince.ToUniversalTime().AddDays(user.SubscriptionDuration);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.CancelSubscription, new[] { user.Email });
|
||||
}
|
||||
public Task<Subscription[]> GetAvailable()
|
||||
{
|
||||
return Task.FromResult(
|
||||
SubscriptionRepository
|
||||
.Get()
|
||||
.Include(x => x.Limits)
|
||||
.ToArray()
|
||||
);
|
||||
}
|
||||
public Task<string> GenerateBuyUrl(Subscription subscription)
|
||||
{
|
||||
var url = ConfigService
|
||||
.GetSection("Moonlight")
|
||||
.GetSection("Payments")
|
||||
.GetValue<string>("BaseUrl");
|
||||
if (subscriptionEnd > DateTime.UtcNow)
|
||||
{
|
||||
return user.CurrentSubscription;
|
||||
}
|
||||
|
||||
return Task.FromResult<string>($"{url}/products/{subscription.SellPassId}");
|
||||
}
|
||||
public Task<string> ProcessGenerate(int subscriptionId)
|
||||
{
|
||||
var subscription = SubscriptionRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == subscriptionId);
|
||||
|
||||
if (subscription == null)
|
||||
throw new DisplayException("Unknown subscription id");
|
||||
|
||||
var token = OneTimeJwtService.Generate(
|
||||
options =>
|
||||
{
|
||||
options.Add("id", subscription.Id.ToString());
|
||||
}
|
||||
);
|
||||
|
||||
return Task.FromResult(token);
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task ApplyCode(string code)
|
||||
{
|
||||
var user = (await IdentityService.Get())!;
|
||||
var values = await OneTimeJwtService.Validate(code);
|
||||
var data = await OneTimeJwtService.Validate(code);
|
||||
|
||||
if (values == null)
|
||||
throw new DisplayException("Invalid subscription code");
|
||||
if (data == null)
|
||||
throw new DisplayException("Invalid or expired subscription code");
|
||||
|
||||
if (!values.ContainsKey("id"))
|
||||
throw new DisplayException("Subscription code is missing the id");
|
||||
|
||||
var id = int.Parse(values["id"]);
|
||||
var id = int.Parse(data["subscription"]);
|
||||
var duration = int.Parse(data["duration"]);
|
||||
|
||||
var subscription = SubscriptionRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (subscription == null)
|
||||
throw new DisplayException("The subscription the code is referring does not exist");
|
||||
throw new DisplayException("The subscription the code is associated with does not exist");
|
||||
|
||||
user.Subscription = subscription;
|
||||
user.SubscriptionDuration = subscription.Duration;
|
||||
user.SubscriptionSince = DateTime.Now;
|
||||
var user = await GetCurrentUser();
|
||||
|
||||
if (user == null)
|
||||
throw new DisplayException("Unable to determine current user");
|
||||
|
||||
user.CurrentSubscription = subscription;
|
||||
user.SubscriptionDuration = duration;
|
||||
user.SubscriptionSince = DateTime.UtcNow;
|
||||
|
||||
UserRepository.Update(user);
|
||||
|
||||
await OneTimeJwtService.Revoke(code);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.ApplySubscriptionCode, new[] { user.Email, subscription.Id.ToString() });
|
||||
await OneTimeJwtService.Revoke(code);
|
||||
}
|
||||
|
||||
public async Task<SubscriptionLimit> GetLimit(string identifier)
|
||||
{
|
||||
var configSection = ConfigService.GetSection("Moonlight").GetSection("Subscriptions");
|
||||
|
||||
var defaultLimits = configSection.GetValue<SubscriptionLimit[]>("defaultLimits");
|
||||
|
||||
var subscription = await GetCurrent();
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier);
|
||||
|
||||
if (foundDefault != null)
|
||||
return foundDefault;
|
||||
|
||||
return new()
|
||||
{
|
||||
Identifier = identifier,
|
||||
Amount = 0
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var subscriptionLimits =
|
||||
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson)
|
||||
?? Array.Empty<SubscriptionLimit>();
|
||||
|
||||
var foundLimit = subscriptionLimits.FirstOrDefault(x => x.Identifier == identifier);
|
||||
|
||||
if (foundLimit != null)
|
||||
return foundLimit;
|
||||
|
||||
var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier);
|
||||
|
||||
if (foundDefault != null)
|
||||
return foundDefault;
|
||||
|
||||
return new()
|
||||
{
|
||||
Identifier = identifier,
|
||||
Amount = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<User?> GetCurrentUser()
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
|
||||
if (user == null)
|
||||
return null;
|
||||
|
||||
var userWithData = UserRepository
|
||||
.Get()
|
||||
.Include(x => x.CurrentSubscription)
|
||||
.First(x => x.Id == user.Id);
|
||||
|
||||
return userWithData;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.LogServices;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
@ -46,13 +47,23 @@ public class TotpService
|
|||
public async Task Enable()
|
||||
{
|
||||
var user = (await IdentityService.Get())!;
|
||||
|
||||
user.TotpEnabled = true;
|
||||
|
||||
user.TotpSecret = GenerateSecret();
|
||||
|
||||
UserRepository.Update(user);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.EnableTotp, user.Email);
|
||||
await AuditLogService.Log(AuditLogType.EnableTotp, x =>
|
||||
{
|
||||
x.Add<User>(user.Email);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task EnforceTotpLogin()
|
||||
{
|
||||
var user = (await IdentityService.Get())!;
|
||||
|
||||
user.TotpEnabled = true;
|
||||
UserRepository.Update(user);
|
||||
}
|
||||
|
||||
public async Task Disable()
|
||||
|
@ -63,7 +74,10 @@ public class TotpService
|
|||
|
||||
UserRepository.Update(user);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.DisableTotp, user.Email);
|
||||
await AuditLogService.Log(AuditLogType.DisableTotp,x =>
|
||||
{
|
||||
x.Add<User>(user.Email);
|
||||
});
|
||||
}
|
||||
|
||||
private string GenerateSecret()
|
||||
|
|
|
@ -77,7 +77,10 @@ public class UserService
|
|||
});
|
||||
|
||||
await MailService.SendMail(user!, "register", values => {});
|
||||
await AuditLogService.Log(AuditLogType.Register, user.Email);
|
||||
await AuditLogService.Log(AuditLogType.Register, x =>
|
||||
{
|
||||
x.Add<User>(user.Email);
|
||||
});
|
||||
|
||||
return await GenerateToken(user);
|
||||
}
|
||||
|
@ -91,7 +94,11 @@ public class UserService
|
|||
|
||||
if (user == null)
|
||||
{
|
||||
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
|
||||
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
||||
{
|
||||
x.Add<User>(email);
|
||||
x.Add<string>(password);
|
||||
});
|
||||
throw new DisplayException("Email and password combination not found");
|
||||
}
|
||||
|
||||
|
@ -100,7 +107,11 @@ public class UserService
|
|||
return user.TotpEnabled;
|
||||
}
|
||||
|
||||
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
|
||||
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
||||
{
|
||||
x.Add<User>(email);
|
||||
x.Add<string>(password);
|
||||
});
|
||||
throw new DisplayException("Email and password combination not found");;
|
||||
}
|
||||
|
||||
|
@ -125,18 +136,28 @@ public class UserService
|
|||
|
||||
if (totpCodeValid)
|
||||
{
|
||||
await AuditLogService.Log(AuditLogType.Login, email);
|
||||
await AuditLogService.Log(AuditLogType.Login, x =>
|
||||
{
|
||||
x.Add<User>(email);
|
||||
});
|
||||
return await GenerateToken(user, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
|
||||
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
||||
{
|
||||
x.Add<User>(email);
|
||||
x.Add<string>(password);
|
||||
});
|
||||
throw new DisplayException("2FA code invalid");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await AuditLogService.Log(AuditLogType.Login, email);
|
||||
await AuditLogService.Log(AuditLogType.Login, x =>
|
||||
{
|
||||
x.Add<User>(email);
|
||||
});
|
||||
return await GenerateToken(user!, true);
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +170,10 @@ public class UserService
|
|||
|
||||
if (isSystemAction)
|
||||
{
|
||||
await AuditLogService.LogSystem(AuditLogType.ChangePassword, user.Email);
|
||||
await AuditLogService.LogSystem(AuditLogType.ChangePassword, x=>
|
||||
{
|
||||
x.Add<User>(user.Email);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -160,7 +184,10 @@ public class UserService
|
|||
values.Add("Location", "In your walls");
|
||||
});
|
||||
|
||||
await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
|
||||
await AuditLogService.Log(AuditLogType.ChangePassword, x =>
|
||||
{
|
||||
x.Add<User>(user.Email);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,17 +197,27 @@ public class UserService
|
|||
|
||||
if (user == null)
|
||||
{
|
||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, id);
|
||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
||||
{
|
||||
x.Add<int>(id);
|
||||
});
|
||||
throw new Exception("Invalid username");
|
||||
}
|
||||
|
||||
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
|
||||
{
|
||||
await AuditLogService.LogSystem(AuditLogType.Login, user.Email);
|
||||
await AuditLogService.LogSystem(AuditLogType.Login, x =>
|
||||
{
|
||||
x.Add<User>(user.Email);
|
||||
});
|
||||
return user;
|
||||
}
|
||||
|
||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, new[] { id.ToString(), password });
|
||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
||||
{
|
||||
x.Add<int>(id);
|
||||
x.Add<string>(password);
|
||||
});
|
||||
throw new Exception("Invalid userid or password");
|
||||
}
|
||||
|
||||
|
@ -218,7 +255,7 @@ public class UserService
|
|||
var newPassword = StringHelper.GenerateString(16);
|
||||
await ChangePassword(user, newPassword, true);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.PasswordReset);
|
||||
await AuditLogService.Log(AuditLogType.PasswordReset, x => {});
|
||||
|
||||
await MailService.SendMail(user, "passwordReset", values =>
|
||||
{
|
||||
|
|
|
@ -58,6 +58,8 @@
|
|||
<_ContentIncludedByDefault Remove="wwwroot\css\site.css" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\Tables\Column.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\Tables\Table.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Servers\Cleanup\Exceptions\Add.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Servers\Cleanup\Exceptions\Edit.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -66,7 +68,6 @@
|
|||
<Folder Include="App\Models\Google\Resources" />
|
||||
<Folder Include="App\Services\DiscordBot\Commands" />
|
||||
<Folder Include="resources\lang" />
|
||||
<Folder Include="wwwroot\assets\media" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
<script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
|
||||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
<script src="/_content/BlazorMonaco/jsInterop.js"></script>
|
||||
<script src="/assets/js/monacoTheme.js"></script>
|
||||
|
||||
<script src="/assets/js/scripts.bundle.js"></script>
|
||||
<script src="/assets/js/flashbang.js"></script>
|
||||
|
@ -112,5 +113,6 @@
|
|||
<script src="/assets/js/loggingUtils.js"></script>
|
||||
<script src="/assets/js/snow.js"></script>
|
||||
<script src="/assets/js/recaptcha.js"></script>
|
||||
<script src="/assets/js/moonlight.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -9,7 +9,6 @@ using Moonlight.App.Repositories;
|
|||
using Moonlight.App.Repositories.Domains;
|
||||
using Moonlight.App.Repositories.LogEntries;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
using Moonlight.App.Repositories.Subscriptions;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.DiscordBot;
|
||||
using Moonlight.App.Services.Interop;
|
||||
|
@ -58,13 +57,12 @@ namespace Moonlight
|
|||
builder.Services.AddScoped<SupportMessageRepository>();
|
||||
builder.Services.AddScoped<DomainRepository>();
|
||||
builder.Services.AddScoped<SharedDomainRepository>();
|
||||
builder.Services.AddScoped<SubscriptionRepository>();
|
||||
builder.Services.AddScoped<SubscriptionLimitRepository>();
|
||||
builder.Services.AddScoped<RevokeRepository>();
|
||||
builder.Services.AddScoped<NotificationRepository>();
|
||||
builder.Services.AddScoped<AaPanelRepository>();
|
||||
builder.Services.AddScoped<WebsiteRepository>();
|
||||
builder.Services.AddScoped<DdosAttackRepository>();
|
||||
builder.Services.AddScoped<SubscriptionRepository>();
|
||||
|
||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
||||
|
@ -89,14 +87,19 @@ namespace Moonlight
|
|||
builder.Services.AddSingleton<ResourceService>();
|
||||
builder.Services.AddScoped<DomainService>();
|
||||
builder.Services.AddScoped<OneTimeJwtService>();
|
||||
builder.Services.AddScoped<SubscriptionService>();
|
||||
builder.Services.AddSingleton<NotificationServerService>();
|
||||
builder.Services.AddScoped<NotificationAdminService>();
|
||||
builder.Services.AddScoped<NotificationClientService>();
|
||||
builder.Services.AddScoped<ModalService>();
|
||||
|
||||
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||
|
||||
builder.Services.AddScoped<SubscriptionService>();
|
||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
||||
|
||||
builder.Services.AddSingleton<CleanupService>();
|
||||
|
||||
// Loggers
|
||||
builder.Services.AddScoped<SecurityLogService>();
|
||||
builder.Services.AddScoped<AuditLogService>();
|
||||
|
@ -153,6 +156,9 @@ namespace Moonlight
|
|||
// Support service
|
||||
var supportServerService = app.Services.GetRequiredService<SupportServerService>();
|
||||
|
||||
// cleanup service
|
||||
_ = app.Services.GetRequiredService<CleanupService>();
|
||||
|
||||
// Discord bot service
|
||||
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@using Moonlight.App.Repositories
|
||||
@using Newtonsoft.Json
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Models.Log
|
||||
|
||||
@inject UserRepository UserRepository
|
||||
|
||||
|
@ -18,7 +19,7 @@
|
|||
<div class="fs-5 fw-semibold mb-2">
|
||||
@if (User == null)
|
||||
{
|
||||
<TL>Password change for</TL> @(Data[0])
|
||||
<TL>Password change for</TL> @(Data[0].Value)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -38,18 +39,18 @@
|
|||
public AuditLogEntry Entry { get; set; }
|
||||
|
||||
private User? User;
|
||||
private string[] Data;
|
||||
private LogData[] Data;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
||||
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
|
||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@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
|
||||
|
@ -18,11 +19,11 @@
|
|||
<div class="fs-5 fw-semibold mb-2">
|
||||
@if (Server == null)
|
||||
{
|
||||
<TL>Change power state for</TL> @(Data[0]) <TL>to</TL> @(Data[1])
|
||||
<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])
|
||||
<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">
|
||||
|
@ -38,18 +39,18 @@
|
|||
public AuditLogEntry Entry { get; set; }
|
||||
|
||||
private Server? Server;
|
||||
private string[] Data;
|
||||
private LogData[] Data;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
||||
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]));
|
||||
Server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == Guid.Parse(Data[0].Value));
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@using Moonlight.App.Repositories
|
||||
@using Newtonsoft.Json
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Models.Log
|
||||
|
||||
@inject UserRepository UserRepository
|
||||
|
||||
|
@ -18,7 +19,7 @@
|
|||
<div class="fs-5 fw-semibold mb-2">
|
||||
@if (User == null)
|
||||
{
|
||||
<TL>New login for</TL> @(Data[0])
|
||||
<TL>New login for</TL> @(Data[0].Value)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -38,18 +39,18 @@
|
|||
public AuditLogEntry Entry { get; set; }
|
||||
|
||||
private User? User;
|
||||
private string[] Data;
|
||||
private LogData[] Data;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
||||
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
|
||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@using Moonlight.App.Repositories
|
||||
@using Newtonsoft.Json
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Models.Log
|
||||
|
||||
@inject UserRepository UserRepository
|
||||
|
||||
|
@ -18,7 +19,7 @@
|
|||
<div class="fs-5 fw-semibold mb-2">
|
||||
@if (User == null)
|
||||
{
|
||||
<TL>Register for</TL> @(Data[0])
|
||||
<TL>Register for</TL> @(Data[0].Value)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -38,18 +39,18 @@
|
|||
public AuditLogEntry Entry { get; set; }
|
||||
|
||||
private User? User;
|
||||
private string[] Data;
|
||||
private LogData[] Data;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
||||
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
|
||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,12 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Exceptions
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Models.Misc
|
||||
@using Moonlight.App.Services.OAuth2
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using Moonlight.App.Models.Forms
|
||||
|
||||
@inject AlertService AlertService
|
||||
@inject UserService UserService
|
||||
|
@ -24,10 +28,10 @@
|
|||
<div class="card rounded-3 w-md-550px">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-center flex-column-fluid pb-15 pb-lg-20">
|
||||
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework" novalidate="novalidate">
|
||||
@if (!TotpRequired)
|
||||
{
|
||||
<div class="text-center mb-11">
|
||||
@if (!TotpRequired)
|
||||
{
|
||||
<SmartForm Model="LoginData" OnValidSubmit="DoLogin">
|
||||
<div class="text-center mt-3 mb-11">
|
||||
<h1 class="text-dark fw-bolder mb-3">
|
||||
<TL>Sign In</TL>
|
||||
</h1>
|
||||
|
@ -61,12 +65,12 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div class="fv-row mb-8 fv-plugins-icon-container">
|
||||
<input @bind="Email" type="text" placeholder="@(SmartTranslateService.Translate("Email"))" class="form-control bg-transparent">
|
||||
<div class="mt-3 mb-3">
|
||||
<InputText @bind-Value="LoginData.Email" type="email" placeholder="@(SmartTranslateService.Translate("Email"))" class="form-control bg-transparent"/>
|
||||
</div>
|
||||
|
||||
<div class="fv-row mb-3 fv-plugins-icon-container">
|
||||
<input @bind="Password" type="password" placeholder="@(SmartTranslateService.Translate("Password"))" class="form-control bg-transparent">
|
||||
<div class="mb-3">
|
||||
<InputText @bind-Value="LoginData.Password" type="password" placeholder="@(SmartTranslateService.Translate("Password"))" class="form-control bg-transparent"/>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-stack flex-wrap gap-3 fs-base fw-semibold mb-8">
|
||||
|
@ -78,35 +82,33 @@
|
|||
</div>
|
||||
|
||||
<div class="d-grid mb-10">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Sign-in"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="DoLogin">
|
||||
</WButton>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<TL>Sign-in</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<div class="text-gray-500 text-center fw-semibold fs-6">
|
||||
<TL>Not registered yet?</TL>
|
||||
|
||||
<a href="/register" class="link-primary">
|
||||
<TL>Sign up</TL>
|
||||
</a>
|
||||
</div>
|
||||
</SmartForm>
|
||||
}
|
||||
else
|
||||
{
|
||||
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
|
||||
<div class="fv-row mb-8 fv-plugins-icon-container">
|
||||
<input type="number" class="form-control bg-transparent">
|
||||
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent"></InputText>
|
||||
</div>
|
||||
<div class="d-grid mb-10">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Sign-in"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="DoLogin">
|
||||
</WButton>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<TL>Sign-in</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="text-gray-500 text-center fw-semibold fs-6">
|
||||
<TL>Not registered yet?</TL>
|
||||
|
||||
<a href="/register" class="link-primary">
|
||||
<TL>Sign up</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</SmartForm>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,37 +116,54 @@
|
|||
|
||||
@code
|
||||
{
|
||||
private string Email = "";
|
||||
private string Password = "";
|
||||
private LoginDataModel LoginData = new();
|
||||
private LoginTotpDataModel TotpData = new();
|
||||
|
||||
private bool TotpRequired = false;
|
||||
private string TotpCode = "";
|
||||
|
||||
private async Task DoLogin()
|
||||
{
|
||||
try
|
||||
{
|
||||
Email = Email.ToLower().Trim();
|
||||
|
||||
TotpRequired = await UserService.CheckTotp(Email, Password);
|
||||
LoginData.Email = LoginData.Email.ToLower().Trim();
|
||||
|
||||
if (!TotpRequired)
|
||||
if (string.IsNullOrEmpty(TotpData.Code))
|
||||
{
|
||||
var token = await UserService.Login(Email, Password);
|
||||
TotpRequired = await UserService.CheckTotp(LoginData.Email, LoginData.Password);
|
||||
|
||||
if (!TotpRequired)
|
||||
{
|
||||
var token = await UserService.Login(LoginData.Email, LoginData.Password);
|
||||
await CookieService.SetValue("token", token, 10);
|
||||
|
||||
if (NavigationManager.Uri.EndsWith("login"))
|
||||
NavigationManager.NavigateTo("/", true);
|
||||
else
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var token = await UserService.Login(LoginData.Email, LoginData.Password, TotpData.Code);
|
||||
await CookieService.SetValue("token", token, 10);
|
||||
|
||||
if(NavigationManager.Uri.EndsWith("login"))
|
||||
|
||||
if (NavigationManager.Uri.EndsWith("login"))
|
||||
NavigationManager.NavigateTo("/", true);
|
||||
else
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
catch (DisplayException e)
|
||||
{
|
||||
// Reset state
|
||||
LoginData = new();
|
||||
TotpData = new();
|
||||
TotpRequired = false;
|
||||
|
||||
await AlertService.Error(
|
||||
SmartTranslateService.Translate("Error"),
|
||||
SmartTranslateService.Translate(e.Message)
|
||||
|
@ -152,6 +171,11 @@
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Reset state
|
||||
LoginData = new();
|
||||
TotpData = new();
|
||||
TotpRequired = false;
|
||||
|
||||
await AlertService.Error(
|
||||
SmartTranslateService.Translate("Error"),
|
||||
SmartTranslateService.Translate("An error occured while logging you in")
|
||||
|
@ -167,7 +191,7 @@
|
|||
var url = await GoogleOAuth2Service.GetUrl();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
}
|
||||
|
||||
|
||||
private async Task DoDiscord()
|
||||
{
|
||||
var url = await DiscordOAuth2Service.GetUrl();
|
||||
|
|
64
Moonlight/Shared/Components/Auth/PasswordChangeView.razor
Normal file
64
Moonlight/Shared/Components/Auth/PasswordChangeView.razor
Normal file
|
@ -0,0 +1,64 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Models.Forms
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Models.Misc
|
||||
@using Moonlight.App.Repositories
|
||||
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject IdentityService IdentityService
|
||||
@inject UserService UserService
|
||||
@inject UserRepository UserRepository
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<div class="d-flex flex-center">
|
||||
<div class="card rounded-3 w-md-550px">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-center flex-column-fluid">
|
||||
<LazyLoader Load="Load">
|
||||
<SmartForm Model="Password" OnValidSubmit="DoChange">
|
||||
<div class="text-center mt-3 mb-11">
|
||||
<h1 class="text-dark fw-bolder mb-3">
|
||||
<TL>Change your password</TL>
|
||||
</h1>
|
||||
<div class="text-gray-500 fw-semibold fs-6">
|
||||
<TL>You need to change your password in order to use moonlight</TL>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-9">
|
||||
<div class="col-md-9">
|
||||
<InputText @bind-Value="Password.Password" type="password" placeholder="@(SmartTranslateService.Translate("New password"))" class="form-control bg-transparent"/>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-primary float-end">
|
||||
<TL>Change</TL>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private PasswordModel Password = new();
|
||||
private User User;
|
||||
|
||||
private async Task Load(LazyLoader loader)
|
||||
{
|
||||
User = await IdentityService.Get();
|
||||
}
|
||||
|
||||
private async Task DoChange()
|
||||
{
|
||||
await UserService.ChangePassword(User, Password.Password);
|
||||
User.Status = UserStatus.Unverified;
|
||||
UserRepository.Update(User);
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
}
|
67
Moonlight/Shared/Components/Auth/UserDataSetView.razor
Normal file
67
Moonlight/Shared/Components/Auth/UserDataSetView.razor
Normal file
|
@ -0,0 +1,67 @@
|
|||
@using Microsoft.AspNetCore.Components
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Models.Forms
|
||||
@using Moonlight.App.Models.Misc
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Sessions
|
||||
|
||||
@inject IdentityService IdentityService
|
||||
@inject UserRepository UserRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<div class="d-flex flex-center">
|
||||
<div class="card rounded-3 w-md-550px">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-center flex-column-fluid">
|
||||
<LazyLoader Load="Load">
|
||||
<SmartForm Model="Name" OnValidSubmit="SetName">
|
||||
<div class="text-center mt-3 mb-11">
|
||||
<h1 class="text-dark fw-bolder mb-3">
|
||||
<TL>Enter your information</TL>
|
||||
</h1>
|
||||
<div class="text-gray-500 fw-semibold fs-6">
|
||||
<TL>You need to enter your full name in order to use moonlight</TL>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col">
|
||||
<InputText @bind-Value="Name.FirstName" type="text" placeholder="@(SmartTranslateService.Translate("First name"))" class="form-control bg-transparent"/>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<InputText @bind-Value="Name.LastName" type="text" placeholder="@(SmartTranslateService.Translate("Last name"))" class="form-control bg-transparent"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary float-end mt-3">
|
||||
<TL>Change</TL>
|
||||
</button>
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private User User;
|
||||
private NameModel Name = new ();
|
||||
|
||||
private async Task Load(LazyLoader loader)
|
||||
{
|
||||
User = await IdentityService.Get();
|
||||
}
|
||||
|
||||
private async Task SetName()
|
||||
{
|
||||
User.FirstName = Name.FirstName;
|
||||
User.LastName = Name.LastName;
|
||||
User.Status = UserStatus.Unverified;
|
||||
|
||||
UserRepository.Update(User);
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ else
|
|||
{
|
||||
receivedExceptions.Add(exception);
|
||||
|
||||
await ErrorLogService.Log(exception);
|
||||
await ErrorLogService.Log(exception, x => {});
|
||||
|
||||
await base.OnErrorAsync(exception);
|
||||
}
|
||||
|
|
|
@ -3,27 +3,30 @@
|
|||
@using Moonlight.Shared.Components.Partials
|
||||
|
||||
@inject SmartTranslateService TranslationService
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
<div class="card-body">
|
||||
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
|
||||
</div>
|
||||
|
||||
@if (!HideControls)
|
||||
{
|
||||
<div class="card-footer pt-0">
|
||||
<div class="btn-group">
|
||||
<WButton
|
||||
Text="@(TranslationService.Translate("Save"))"
|
||||
WorkingText="@(TranslationService.Translate("Saving"))"
|
||||
OnClick="Submit"></WButton>
|
||||
<WButton
|
||||
CssClasses="btn-danger"
|
||||
Text="@(TranslationService.Translate("Cancel"))"
|
||||
WorkingText="@(TranslationService.Translate("Canceling"))"
|
||||
OnClick="Cancel"></WButton>
|
||||
</div>
|
||||
<div class="card bg-black rounded">
|
||||
<div class="card-body">
|
||||
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!HideControls)
|
||||
{
|
||||
<div class="card-footer">
|
||||
<div class="btn-group">
|
||||
<WButton
|
||||
Text="@(TranslationService.Translate("Save"))"
|
||||
WorkingText="@(TranslationService.Translate("Saving"))"
|
||||
OnClick="Submit"></WButton>
|
||||
<WButton
|
||||
CssClasses="btn-danger"
|
||||
Text="@(TranslationService.Translate("Cancel"))"
|
||||
WorkingText="@(TranslationService.Translate("Canceling"))"
|
||||
OnClick="Cancel"></WButton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
@ -53,8 +56,8 @@
|
|||
{
|
||||
AutomaticLayout = true,
|
||||
Language = "plaintext",
|
||||
Value = "Wird geladen",
|
||||
Theme = "vs-dark",
|
||||
Value = "Loading content",
|
||||
Theme = "moonlight-theme",
|
||||
Contextmenu = false,
|
||||
Minimap = new()
|
||||
{
|
||||
|
@ -68,6 +71,8 @@
|
|||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("initMonacoTheme");
|
||||
|
||||
Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () =>
|
||||
{
|
||||
EditorOptions.Language = Language;
|
||||
|
|
|
@ -1,529 +1,329 @@
|
|||
@using Moonlight.App.Helpers
|
||||
@using BlazorContextMenu
|
||||
@using BlazorDownloadFile
|
||||
@using Moonlight.App.Helpers.Files
|
||||
@using Moonlight.App.Helpers
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Models.Files
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using BlazorDownloadFile
|
||||
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject SmartTranslateService TranslationService
|
||||
@inject AlertService AlertService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject IBlazorDownloadFileService FileService
|
||||
|
||||
<div class="card card-flush">
|
||||
@if (Editing == null)
|
||||
@if (Editing)
|
||||
{
|
||||
<div class="card-header pt-8">
|
||||
<div class="card-title">
|
||||
<div class="d-flex align-items-center position-relative my-1">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-6">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.5" x="17.0365" y="15.1223" width="8.15546" height="2" rx="1" transform="rotate(45 17.0365 15.1223)" fill="currentColor"></rect>
|
||||
<path d="M11 19C6.55556 19 3 15.4444 3 11C3 6.55556 6.55556 3 11 3C15.4444 3 19 6.55556 19 11C19 15.4444 15.4444 19 11 19ZM11 5C7.53333 5 5 7.53333 5 11C5 14.4667 7.53333 17 11 17C14.4667 17 17 14.4667 17 11C17 7.53333 14.4667 5 11 5Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" @bind="Search" @bind:event="oninput" class="form-control form-control-solid w-250px ps-15" placeholder="@(TranslationService.Translate("Search files and folders"))">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
@if (SelectedFiles.Count == 0)
|
||||
{
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-muted svg-icon-2hx">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
|
||||
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Launch WinSCP</TL>
|
||||
</button>
|
||||
|
||||
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>New folder</TL>
|
||||
</button>
|
||||
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||
<label for="fileUpload" class="btn btn-primary me-3 pt-5 @(Uploading ? "disabled" : "")">
|
||||
@if (Uploading)
|
||||
{
|
||||
<span>@(Percent)%</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Upload</TL>
|
||||
}
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<div class="fw-bold me-5">
|
||||
<span class="me-2">
|
||||
@(SelectedFiles.Count)
|
||||
</span>
|
||||
<TL>Selected</TL>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary me-3">
|
||||
<TL>Move deleted</TL>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger">
|
||||
<TL>Delete selected</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="badge badge-lg badge-light-primary">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
@{
|
||||
var vx = "/";
|
||||
}
|
||||
<a @onclick:preventDefault @onclick="() => SetPath(vx)" href="#">/</a>
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
@{
|
||||
var cp = "/";
|
||||
var lp = "/";
|
||||
var pathParts = CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var path in pathParts)
|
||||
{
|
||||
lp = cp;
|
||||
<a @onclick:preventDefault @onclick="() => SetPath(lp)" href="#">@(path)</a>
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
cp += path + "/";
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dt-bootstrap4 no-footer">
|
||||
@if (Loading)
|
||||
{
|
||||
<div class="mt-5 alert alert-info">
|
||||
<span>
|
||||
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="w-10px pe-2">
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
|
||||
<input class="form-check-input" type="checkbox" @onchange="OnAllFileToggle">
|
||||
</div>
|
||||
</th>
|
||||
<th class="min-w-250px">
|
||||
<TL>File name</TL>
|
||||
</th>
|
||||
<th class="min-w-10px">
|
||||
<TL>File size</TL>
|
||||
</th>
|
||||
<th class="min-w-125px">
|
||||
<TL>Last modified</TL>
|
||||
</th>
|
||||
<th class="w-125px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-600">
|
||||
@foreach (var obj in Objects.Where(x => x.Name.Contains(Search)))
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
||||
<input class="form-check-input" type="checkbox" @onchange="(e) => OnFileToggle(e, obj)">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
@if (obj.IsFile)
|
||||
{
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M19 22H5C4.4 22 4 21.6 4 21V3C4 2.4 4.4 2 5 2H14L20 8V21C20 21.6 19.6 22 19 22Z" fill="currentColor"></path>
|
||||
<path d="M15 8H20L14 2V7C14 7.6 14.4 8 15 8Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M9.2 3H3C2.4 3 2 3.4 2 4V19C2 19.6 2.4 20 3 20H21C21.6 20 22 19.6 22 19V7C22 6.4 21.6 6 21 6H12L10.4 3.60001C10.2 3.20001 9.7 3 9.2 3Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
|
||||
@if (obj.IsFile)
|
||||
{
|
||||
<a href="#" @onclick:preventDefault @onclick="() => OpenFile(obj)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="#" @onclick:preventDefault @onclick="() => CdPath(obj.Name)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@(Formatter.FormatSize(obj.Size))
|
||||
</td>
|
||||
<td>
|
||||
@(obj.UpdatedAt.ToShortDateString()) @(obj.UpdatedAt.ToShortTimeString())
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="ms-2">
|
||||
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="obj">
|
||||
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
||||
<span class="svg-icon svg-icon-5 m-0">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<FileEditor @ref="Editor"
|
||||
InitialData="@EditorInitialData"
|
||||
Language="@EditorLanguage"
|
||||
OnCancel="() => Cancel()"
|
||||
OnSubmit="(_) => Cancel(true)"
|
||||
HideControls="false">
|
||||
</FileEditor>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Loading)
|
||||
{
|
||||
<div class="mt-5 alert alert-info">
|
||||
<span>
|
||||
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<FileEditor OnSubmit="SaveFile" OnCancel="CloseFile" InitialData="@(InitialEditorData)" Language="@(Language)"></FileEditor>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="card mb-7">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="d-flex flex-stack">
|
||||
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
@if (View != null && View.SelectedFiles.Any())
|
||||
{
|
||||
<div class="fw-bold me-5">
|
||||
<span class="me-2">@(View.SelectedFiles.Length) <TL>selected</TL></span>
|
||||
</div>
|
||||
|
||||
<WButton Text="@(SmartTranslateService.Translate("Move"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Moving"))"
|
||||
CssClasses="btn-primary me-3"
|
||||
OnClick="StartMoveFiles">
|
||||
</WButton>
|
||||
|
||||
<WButton Text="@(SmartTranslateService.Translate("Compress"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Compressing"))"
|
||||
CssClasses="btn-primary me-3"
|
||||
OnClick="CompressMultiple">
|
||||
</WButton>
|
||||
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-danger"
|
||||
OnClick="DeleteMultiple">
|
||||
</WButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-muted svg-icon-2hx">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
|
||||
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Launch WinSCP</TL>
|
||||
</button>
|
||||
|
||||
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
|
||||
<Item Id="rename" OnClick="OnContextMenuClick"><TL>Rename</TL></Item>
|
||||
<Item Id="move" OnClick="OnContextMenuClick"><TL>Move</TL></Item>
|
||||
<Item Id="archive" OnClick="OnContextMenuClick"><TL>Archive</TL></Item>
|
||||
<Item Id="unarchive" OnClick="OnContextMenuClick"><TL>Unarchive</TL></Item>
|
||||
<Item Id="download" OnClick="OnContextMenuClick"><TL>Download</TL></Item>
|
||||
<Item Id="delete" OnClick="OnContextMenuClick"><TL>Delete</TL></Item>
|
||||
</ContextMenu>
|
||||
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>New folder</TL>
|
||||
</button>
|
||||
|
||||
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-body">
|
||||
<FileView @ref="View"
|
||||
Access="Access"
|
||||
ContextActions="Actions"
|
||||
OnSelectionChanged="OnSelectionChanged"
|
||||
OnElementClicked="OnElementClicked"
|
||||
DisableScrolling="true">
|
||||
</FileView>
|
||||
</div>
|
||||
|
||||
<FileSelectModal @ref="FileSelectModal"
|
||||
OnlyFolder="true"
|
||||
Title="@(SmartTranslateService.Translate("Select folder to move the file(s) to"))"
|
||||
Access="MoveAccess"
|
||||
OnSubmit="OnFileMoveSubmit">
|
||||
</FileSelectModal>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public IFileAccess FileAccess { get; set; }
|
||||
public FileAccess Access { get; set; }
|
||||
|
||||
// Data
|
||||
// File Editor
|
||||
private bool Editing = false;
|
||||
private string EditorInitialData = "";
|
||||
private string EditorLanguage = "";
|
||||
private FileData EditingFile;
|
||||
private FileEditor Editor;
|
||||
|
||||
private List<FileManagerObject> SelectedFiles { get; set; } = new();
|
||||
private List<FileManagerObject> Objects { get; set; } = new();
|
||||
private string CurrentPath = "";
|
||||
// File View
|
||||
private FileView? View;
|
||||
|
||||
// Search
|
||||
private string SearchValue = "";
|
||||
// File Move
|
||||
private FileAccess MoveAccess;
|
||||
private FileSelectModal FileSelectModal;
|
||||
private FileData? SingleMoveFile = null;
|
||||
|
||||
// Config
|
||||
private ContextAction[] Actions = Array.Empty<ContextAction>();
|
||||
|
||||
private string Search
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
get { return SearchValue; }
|
||||
set
|
||||
MoveAccess = (FileAccess)Access.Clone();
|
||||
|
||||
List<ContextAction> actions = new();
|
||||
|
||||
actions.Add(new()
|
||||
{
|
||||
SearchValue = value;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
Id = "rename",
|
||||
Name = "Rename",
|
||||
Action = async (x) =>
|
||||
{
|
||||
var name = await AlertService.Text(
|
||||
SmartTranslateService.Translate("Rename"),
|
||||
SmartTranslateService.Translate("Enter a new name"),
|
||||
x.Name
|
||||
);
|
||||
|
||||
// States
|
||||
private bool Loading = false;
|
||||
|
||||
// States - Editor
|
||||
private FileManagerObject? Editing = null;
|
||||
private string InitialEditorData = "";
|
||||
private string Language = "plaintext";
|
||||
|
||||
// States - File Upload
|
||||
private bool Uploading = false;
|
||||
private int Percent = 0;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
if (name != x.Name)
|
||||
{
|
||||
await Access.Move(x, Access.CurrentPath + name);
|
||||
}
|
||||
|
||||
await View!.Refresh();
|
||||
}
|
||||
});
|
||||
|
||||
actions.Add(new ()
|
||||
{
|
||||
await RefreshActive();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshActive()
|
||||
{
|
||||
Loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Refresh(false);
|
||||
|
||||
Loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Refresh(bool rerender = true)
|
||||
{
|
||||
SelectedFiles.Clear();
|
||||
Objects.Clear();
|
||||
CurrentPath = await FileAccess.GetCurrentPath();
|
||||
|
||||
var data = await FileAccess.GetDirectoryContent();
|
||||
Objects = data.ToList();
|
||||
|
||||
if (rerender)
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task CdPath(string path)
|
||||
{
|
||||
await FileAccess.ChangeDirectory(path);
|
||||
await RefreshActive();
|
||||
}
|
||||
|
||||
private async Task SetPath(string path)
|
||||
{
|
||||
await FileAccess.SetDirectory(path);
|
||||
await RefreshActive();
|
||||
}
|
||||
|
||||
private async Task OnContextMenuClick(ItemClickEventArgs e)
|
||||
{
|
||||
var data = e.Data as FileManagerObject;
|
||||
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
switch (e.MenuItem.Id)
|
||||
{
|
||||
case "delete":
|
||||
await FileAccess.Delete(data);
|
||||
break;
|
||||
case "download":
|
||||
if (data.IsFile)
|
||||
Id = "download",
|
||||
Name = "Download",
|
||||
Action = async (x) =>
|
||||
{
|
||||
if (x.IsFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = await FileAccess.GetDownloadStream(data);
|
||||
await ToastService.Info(TranslationService.Translate("Starting download"));
|
||||
var stream = await Access.DownloadStream(x);
|
||||
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
|
||||
await FileService.AddBuffer(stream);
|
||||
await FileService.DownloadBinaryBuffers(data.Name);
|
||||
await FileService.DownloadBinaryBuffers(x.Name);
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = await FileAccess.GetDownloadUrl(data);
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
await ToastService.Info(TranslationService.Translate("Starting download"));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("Error starting download"));
|
||||
Logger.Error("Error downloading file");
|
||||
Logger.Error(exception.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("Error starting download"));
|
||||
Logger.Error("Error downloading file stream");
|
||||
Logger.Error(exception.Message);
|
||||
var url = await Access.DownloadUrl(x);
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "rename":
|
||||
var newName = await AlertService.Text(TranslationService.Translate("Rename"), TranslationService.Translate("Enter a new name"), data.Name);
|
||||
var path = await FileAccess.GetCurrentPath();
|
||||
await FileAccess.Move(data, path + "/" + newName);
|
||||
break;
|
||||
}
|
||||
|
||||
await Refresh(false);
|
||||
}
|
||||
|
||||
private async Task OnFileToggle(ChangeEventArgs obj, FileManagerObject o)
|
||||
{
|
||||
if ((bool)obj.Value)
|
||||
{
|
||||
if (SelectedFiles.Contains(o))
|
||||
return;
|
||||
|
||||
SelectedFiles.Add(o);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!SelectedFiles.Contains(o))
|
||||
return;
|
||||
|
||||
SelectedFiles.Remove(o);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnAllFileToggle(ChangeEventArgs obj)
|
||||
{
|
||||
if ((bool)obj.Value)
|
||||
{
|
||||
foreach (var o in Objects)
|
||||
{
|
||||
if (SelectedFiles.Contains(o))
|
||||
continue;
|
||||
|
||||
SelectedFiles.Add(o);
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else
|
||||
});
|
||||
|
||||
actions.Add(new()
|
||||
{
|
||||
foreach (var o in Objects)
|
||||
Id = "compress",
|
||||
Name = "Compress",
|
||||
Action = async (x) =>
|
||||
{
|
||||
if (!SelectedFiles.Contains(o))
|
||||
continue;
|
||||
|
||||
SelectedFiles.Remove(o);
|
||||
await Access.Compress(x);
|
||||
await View!.Refresh();
|
||||
}
|
||||
});
|
||||
|
||||
actions.Add(new ()
|
||||
{
|
||||
Id = "decompress",
|
||||
Name = "Decompress",
|
||||
Action = async (x) =>
|
||||
{
|
||||
await Access.Decompress(x);
|
||||
await View!.Refresh();
|
||||
}
|
||||
});
|
||||
|
||||
actions.Add(new()
|
||||
{
|
||||
Id = "move",
|
||||
Name = "Move",
|
||||
Action = async (x) =>
|
||||
{
|
||||
SingleMoveFile = x;
|
||||
await StartMoveFiles();
|
||||
}
|
||||
});
|
||||
|
||||
actions.Add(new()
|
||||
{
|
||||
Id = "delete",
|
||||
Name = "Delete",
|
||||
Action = async (x) =>
|
||||
{
|
||||
await Access.Delete(x);
|
||||
await View!.Refresh();
|
||||
}
|
||||
});
|
||||
|
||||
Actions = actions.ToArray();
|
||||
}
|
||||
|
||||
private async Task<bool> OnElementClicked(FileData fileData)
|
||||
{
|
||||
if (fileData.IsFile)
|
||||
{
|
||||
EditorInitialData = await Access.Read(fileData);
|
||||
EditorLanguage = MonacoTypeHelper.GetEditorType(fileData.Name);
|
||||
EditingFile = fileData;
|
||||
|
||||
Editing = true;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateFolder()
|
||||
{
|
||||
var name = await AlertService.Text(TranslationService.Translate("Create a new folder"), TranslationService.Translate("Enter a name"), "");
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return;
|
||||
|
||||
await FileAccess.CreateDirectory(name);
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private async void SaveFile(string data)
|
||||
{
|
||||
if (Editing == null)
|
||||
return;
|
||||
|
||||
await FileAccess.WriteFile(Editing, data);
|
||||
Editing = null;
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private async Task OpenFile(FileManagerObject o)
|
||||
{
|
||||
Editing = o;
|
||||
Loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
InitialEditorData = await FileAccess.ReadFile(Editing);
|
||||
Language = MonacoTypeHelper.GetEditorType(Editing.Name);
|
||||
|
||||
Loading = false;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
return false;
|
||||
}
|
||||
|
||||
private async void CloseFile()
|
||||
private async void Cancel(bool save = false)
|
||||
{
|
||||
Editing = null;
|
||||
if (save)
|
||||
{
|
||||
var data = await Editor.GetData();
|
||||
await Access.Write(EditingFile, data);
|
||||
}
|
||||
|
||||
Editing = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Launch()
|
||||
{
|
||||
NavigationManager.NavigateTo(await FileAccess.GetLaunchUrl());
|
||||
var url = await Access.GetLaunchUrl();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
}
|
||||
|
||||
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||
private async Task CreateFolder()
|
||||
{
|
||||
var name = await AlertService.Text(
|
||||
SmartTranslateService.Translate("Create a new folder"),
|
||||
SmartTranslateService.Translate("Enter a name"),
|
||||
""
|
||||
);
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return;
|
||||
|
||||
await Access.MkDir(name);
|
||||
await View!.Refresh();
|
||||
}
|
||||
|
||||
private async Task OnSelectionChanged()
|
||||
{
|
||||
Uploading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
foreach (var browserFile in arg.GetMultipleFiles())
|
||||
private async Task StartMoveFiles()
|
||||
{
|
||||
await FileSelectModal.Show();
|
||||
}
|
||||
|
||||
private async Task DeleteMultiple()
|
||||
{
|
||||
foreach (var data in View!.SelectedFiles)
|
||||
{
|
||||
if (browserFile.Size < 1024 * 1024 * 100)
|
||||
{
|
||||
Percent = 0;
|
||||
|
||||
try
|
||||
{
|
||||
await FileAccess.UploadFile(
|
||||
browserFile.Name,
|
||||
browserFile.OpenReadStream(1024 * 1024 * 100),
|
||||
async (i) =>
|
||||
{
|
||||
Percent = i;
|
||||
|
||||
Task.Run(() => { InvokeAsync(StateHasChanged); });
|
||||
});
|
||||
|
||||
await Refresh();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("An unknown error occured while uploading a file"));
|
||||
Logger.Error("Error uploading file");
|
||||
Logger.Error(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("The uploaded file should not be bigger than 100MB"));
|
||||
}
|
||||
await Access.Delete(data);
|
||||
}
|
||||
|
||||
Uploading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await View!.Refresh();
|
||||
}
|
||||
|
||||
await ToastService.Success(TranslationService.Translate("File upload complete"));
|
||||
private async Task CompressMultiple()
|
||||
{
|
||||
await Access.Compress(View!.SelectedFiles);
|
||||
|
||||
await View!.Refresh();
|
||||
}
|
||||
|
||||
private async Task OnFileMoveSubmit(string path)
|
||||
{
|
||||
foreach (var sFile in View!.SelectedFiles)
|
||||
{
|
||||
await Access.Move(sFile, path + sFile.Name);
|
||||
}
|
||||
|
||||
if (SingleMoveFile != null)
|
||||
{
|
||||
await Access.Move(SingleMoveFile, path + SingleMoveFile.Name);
|
||||
SingleMoveFile = null;
|
||||
}
|
||||
|
||||
await View.Refresh();
|
||||
}
|
||||
|
||||
// This method can be called by every component to refresh the view
|
||||
private async Task OnComponentStateChanged()
|
||||
{
|
||||
await View!.Refresh();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,529 @@
|
|||
@using Moonlight.App.Helpers
|
||||
@using BlazorContextMenu
|
||||
@using BlazorDownloadFile
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Models.Files
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject SmartTranslateService TranslationService
|
||||
@inject IBlazorDownloadFileService FileService
|
||||
|
||||
<div class="card card-flush">
|
||||
@if (Editing == null)
|
||||
{
|
||||
<div class="card-header pt-8">
|
||||
<div class="card-title">
|
||||
<div class="d-flex align-items-center position-relative my-1">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-6">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.5" x="17.0365" y="15.1223" width="8.15546" height="2" rx="1" transform="rotate(45 17.0365 15.1223)" fill="currentColor"></rect>
|
||||
<path d="M11 19C6.55556 19 3 15.4444 3 11C3 6.55556 6.55556 3 11 3C15.4444 3 19 6.55556 19 11C19 15.4444 15.4444 19 11 19ZM11 5C7.53333 5 5 7.53333 5 11C5 14.4667 7.53333 17 11 17C14.4667 17 17 14.4667 17 11C17 7.53333 14.4667 5 11 5Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" @bind="Search" @bind:event="oninput" class="form-control form-control-solid w-250px ps-15" placeholder="@(TranslationService.Translate("Search files and folders"))">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
@if (SelectedFiles.Count == 0)
|
||||
{
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-muted svg-icon-2hx">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
|
||||
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Launch WinSCP</TL>
|
||||
</button>
|
||||
|
||||
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>New folder</TL>
|
||||
</button>
|
||||
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||
<label for="fileUpload" class="btn btn-primary me-3 pt-5 @(Uploading ? "disabled" : "")">
|
||||
@if (Uploading)
|
||||
{
|
||||
<span>@(Percent)%</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Upload</TL>
|
||||
}
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<div class="fw-bold me-5">
|
||||
<span class="me-2">
|
||||
@(SelectedFiles.Count)
|
||||
</span>
|
||||
<TL>Selected</TL>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary me-3">
|
||||
<TL>Move deleted</TL>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger">
|
||||
<TL>Delete selected</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="badge badge-lg badge-light-primary">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
@{
|
||||
var vx = "/";
|
||||
}
|
||||
<a @onclick:preventDefault @onclick="() => SetPath(vx)" href="#">/</a>
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
@{
|
||||
var cp = "/";
|
||||
var lp = "/";
|
||||
var pathParts = CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var path in pathParts)
|
||||
{
|
||||
lp = cp;
|
||||
<a @onclick:preventDefault @onclick="() => SetPath(lp)" href="#">@(path)</a>
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
cp += path + "/";
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dt-bootstrap4 no-footer">
|
||||
@if (Loading)
|
||||
{
|
||||
<div class="mt-5 alert alert-info">
|
||||
<span>
|
||||
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="w-10px pe-2">
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
|
||||
<input class="form-check-input" type="checkbox" @onchange="OnAllFileToggle">
|
||||
</div>
|
||||
</th>
|
||||
<th class="min-w-250px">
|
||||
<TL>File name</TL>
|
||||
</th>
|
||||
<th class="min-w-10px">
|
||||
<TL>File size</TL>
|
||||
</th>
|
||||
<th class="min-w-125px">
|
||||
<TL>Last modified</TL>
|
||||
</th>
|
||||
<th class="w-125px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-600">
|
||||
@foreach (var obj in Objects.Where(x => x.Name.Contains(Search)))
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
||||
<input class="form-check-input" type="checkbox" @onchange="(e) => OnFileToggle(e, obj)">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
@if (obj.IsFile)
|
||||
{
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M19 22H5C4.4 22 4 21.6 4 21V3C4 2.4 4.4 2 5 2H14L20 8V21C20 21.6 19.6 22 19 22Z" fill="currentColor"></path>
|
||||
<path d="M15 8H20L14 2V7C14 7.6 14.4 8 15 8Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M9.2 3H3C2.4 3 2 3.4 2 4V19C2 19.6 2.4 20 3 20H21C21.6 20 22 19.6 22 19V7C22 6.4 21.6 6 21 6H12L10.4 3.60001C10.2 3.20001 9.7 3 9.2 3Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
|
||||
@if (obj.IsFile)
|
||||
{
|
||||
<a href="#" @onclick:preventDefault @onclick="() => OpenFile(obj)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="#" @onclick:preventDefault @onclick="() => CdPath(obj.Name)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@(Formatter.FormatSize(obj.Size))
|
||||
</td>
|
||||
<td>
|
||||
@(obj.UpdatedAt.ToShortDateString()) @(obj.UpdatedAt.ToShortTimeString())
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="ms-2">
|
||||
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="obj">
|
||||
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
||||
<span class="svg-icon svg-icon-5 m-0">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Loading)
|
||||
{
|
||||
<div class="mt-5 alert alert-info">
|
||||
<span>
|
||||
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<FileEditor OnSubmit="SaveFile" OnCancel="CloseFile" InitialData="@(InitialEditorData)" Language="@(Language)"></FileEditor>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
|
||||
<Item Id="rename" OnClick="OnContextMenuClick"><TL>Rename</TL></Item>
|
||||
<Item Id="move" OnClick="OnContextMenuClick"><TL>Move</TL></Item>
|
||||
<Item Id="archive" OnClick="OnContextMenuClick"><TL>Archive</TL></Item>
|
||||
<Item Id="unarchive" OnClick="OnContextMenuClick"><TL>Unarchive</TL></Item>
|
||||
<Item Id="download" OnClick="OnContextMenuClick"><TL>Download</TL></Item>
|
||||
<Item Id="delete" OnClick="OnContextMenuClick"><TL>Delete</TL></Item>
|
||||
</ContextMenu>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public IFileAccess FileAccess { get; set; }
|
||||
|
||||
// Data
|
||||
|
||||
private List<FileManagerObject> SelectedFiles { get; set; } = new();
|
||||
private List<FileManagerObject> Objects { get; set; } = new();
|
||||
private string CurrentPath = "";
|
||||
|
||||
// Search
|
||||
private string SearchValue = "";
|
||||
|
||||
private string Search
|
||||
{
|
||||
get { return SearchValue; }
|
||||
set
|
||||
{
|
||||
SearchValue = value;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// States
|
||||
private bool Loading = false;
|
||||
|
||||
// States - Editor
|
||||
private FileManagerObject? Editing = null;
|
||||
private string InitialEditorData = "";
|
||||
private string Language = "plaintext";
|
||||
|
||||
// States - File Upload
|
||||
private bool Uploading = false;
|
||||
private int Percent = 0;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await RefreshActive();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshActive()
|
||||
{
|
||||
Loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Refresh(false);
|
||||
|
||||
Loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Refresh(bool rerender = true)
|
||||
{
|
||||
SelectedFiles.Clear();
|
||||
Objects.Clear();
|
||||
CurrentPath = await FileAccess.GetCurrentPath();
|
||||
|
||||
var data = await FileAccess.GetDirectoryContent();
|
||||
Objects = data.ToList();
|
||||
|
||||
if (rerender)
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task CdPath(string path)
|
||||
{
|
||||
await FileAccess.ChangeDirectory(path);
|
||||
await RefreshActive();
|
||||
}
|
||||
|
||||
private async Task SetPath(string path)
|
||||
{
|
||||
await FileAccess.SetDirectory(path);
|
||||
await RefreshActive();
|
||||
}
|
||||
|
||||
private async Task OnContextMenuClick(ItemClickEventArgs e)
|
||||
{
|
||||
var data = e.Data as FileManagerObject;
|
||||
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
switch (e.MenuItem.Id)
|
||||
{
|
||||
case "delete":
|
||||
await FileAccess.Delete(data);
|
||||
break;
|
||||
case "download":
|
||||
if (data.IsFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = await FileAccess.GetDownloadStream(data);
|
||||
await ToastService.Info(TranslationService.Translate("Starting download"));
|
||||
await FileService.AddBuffer(stream);
|
||||
await FileService.DownloadBinaryBuffers(data.Name);
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = await FileAccess.GetDownloadUrl(data);
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
await ToastService.Info(TranslationService.Translate("Starting download"));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("Error starting download"));
|
||||
Logger.Error("Error downloading file");
|
||||
Logger.Error(exception.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("Error starting download"));
|
||||
Logger.Error("Error downloading file stream");
|
||||
Logger.Error(exception.Message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "rename":
|
||||
var newName = await AlertService.Text(TranslationService.Translate("Rename"), TranslationService.Translate("Enter a new name"), data.Name);
|
||||
var path = await FileAccess.GetCurrentPath();
|
||||
await FileAccess.Move(data, path + "/" + newName);
|
||||
break;
|
||||
}
|
||||
|
||||
await Refresh(false);
|
||||
}
|
||||
|
||||
private async Task OnFileToggle(ChangeEventArgs obj, FileManagerObject o)
|
||||
{
|
||||
if ((bool)obj.Value)
|
||||
{
|
||||
if (SelectedFiles.Contains(o))
|
||||
return;
|
||||
|
||||
SelectedFiles.Add(o);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!SelectedFiles.Contains(o))
|
||||
return;
|
||||
|
||||
SelectedFiles.Remove(o);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnAllFileToggle(ChangeEventArgs obj)
|
||||
{
|
||||
if ((bool)obj.Value)
|
||||
{
|
||||
foreach (var o in Objects)
|
||||
{
|
||||
if (SelectedFiles.Contains(o))
|
||||
continue;
|
||||
|
||||
SelectedFiles.Add(o);
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var o in Objects)
|
||||
{
|
||||
if (!SelectedFiles.Contains(o))
|
||||
continue;
|
||||
|
||||
SelectedFiles.Remove(o);
|
||||
}
|
||||
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateFolder()
|
||||
{
|
||||
var name = await AlertService.Text(TranslationService.Translate("Create a new folder"), TranslationService.Translate("Enter a name"), "");
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return;
|
||||
|
||||
await FileAccess.CreateDirectory(name);
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private async void SaveFile(string data)
|
||||
{
|
||||
if (Editing == null)
|
||||
return;
|
||||
|
||||
await FileAccess.WriteFile(Editing, data);
|
||||
Editing = null;
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private async Task OpenFile(FileManagerObject o)
|
||||
{
|
||||
Editing = o;
|
||||
Loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
InitialEditorData = await FileAccess.ReadFile(Editing);
|
||||
Language = MonacoTypeHelper.GetEditorType(Editing.Name);
|
||||
|
||||
Loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async void CloseFile()
|
||||
{
|
||||
Editing = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Launch()
|
||||
{
|
||||
NavigationManager.NavigateTo(await FileAccess.GetLaunchUrl());
|
||||
}
|
||||
|
||||
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||
{
|
||||
Uploading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
foreach (var browserFile in arg.GetMultipleFiles())
|
||||
{
|
||||
if (browserFile.Size < 1024 * 1024 * 100)
|
||||
{
|
||||
Percent = 0;
|
||||
|
||||
try
|
||||
{
|
||||
await FileAccess.UploadFile(
|
||||
browserFile.Name,
|
||||
browserFile.OpenReadStream(1024 * 1024 * 100),
|
||||
async (i) =>
|
||||
{
|
||||
Percent = i;
|
||||
|
||||
Task.Run(() => { InvokeAsync(StateHasChanged); });
|
||||
});
|
||||
|
||||
await Refresh();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("An unknown error occured while uploading a file"));
|
||||
Logger.Error("Error uploading file");
|
||||
Logger.Error(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("The uploaded file should not be bigger than 100MB"));
|
||||
}
|
||||
}
|
||||
|
||||
Uploading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await ToastService.Success(TranslationService.Translate("File upload complete"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
@using Moonlight.App.Helpers.Files
|
||||
|
||||
<div class="badge badge-lg badge-light-primary">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
@{
|
||||
var vx = "/";
|
||||
}
|
||||
<a @onclick:preventDefault @onclick="() => SetPath(vx)" href="#">/</a>
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
@{
|
||||
var cp = "/";
|
||||
var lp = "/";
|
||||
var pathParts = Access.CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var path in pathParts)
|
||||
{
|
||||
lp = cp;
|
||||
<a @onclick:preventDefault @onclick="() => SetPath(lp)" href="#">@(path)</a>
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
cp += path + "/";
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public FileAccess Access { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnPathChanged { get; set; }
|
||||
|
||||
public async Task SetPath(string path)
|
||||
{
|
||||
await Access.SetDir(path);
|
||||
OnPathChanged?.Invoke();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
@using Moonlight.App.Helpers.Files
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
|
||||
@inject ModalService ModalService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<div class="modal" id="fileView@(Id)" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
@(Title)
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<FileView @ref="FileView"
|
||||
Access="Access"
|
||||
HideSelect="true"
|
||||
Filter="DoFilter"
|
||||
OnElementClicked="OnElementClicked">
|
||||
</FileView>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Submit"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Processing"))"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="Submit">
|
||||
</WButton>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Cancel"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Processing"))"
|
||||
CssClasses="btn-danger"
|
||||
OnClick="Cancel">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public FileAccess Access { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool OnlyFolder { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public Func<FileData, bool>? Filter { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "Select file or folder";
|
||||
|
||||
[Parameter]
|
||||
public Func<string, Task>? OnSubmit { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnCancel { get; set; }
|
||||
|
||||
private int Id = 0;
|
||||
private string Result = "/";
|
||||
|
||||
private FileView FileView;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Id = this.GetHashCode();
|
||||
}
|
||||
|
||||
public async Task Show()
|
||||
{
|
||||
// Reset
|
||||
Result = "/";
|
||||
await Access.SetDir("/");
|
||||
await FileView.Refresh();
|
||||
|
||||
await ModalService.Show("fileView" + Id);
|
||||
}
|
||||
|
||||
public async Task Hide()
|
||||
{
|
||||
await Cancel();
|
||||
}
|
||||
|
||||
private async Task Cancel()
|
||||
{
|
||||
await ModalService.Hide("fileView" + Id);
|
||||
await OnCancel?.Invoke()!;
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await ModalService.Hide("fileView" + Id);
|
||||
await OnSubmit?.Invoke(Result)!;
|
||||
}
|
||||
|
||||
private bool DoFilter(FileData file)
|
||||
{
|
||||
if (OnlyFolder)
|
||||
{
|
||||
if (file.IsFile)
|
||||
return false;
|
||||
else
|
||||
{
|
||||
if (Filter != null)
|
||||
return Filter.Invoke(file);
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Filter != null)
|
||||
return Filter.Invoke(file);
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> OnElementClicked(FileData file)
|
||||
{
|
||||
Result = Access.CurrentPath + file.Name + (file.IsFile ? "" : "/");
|
||||
|
||||
if (!OnlyFolder && file.IsFile)
|
||||
{
|
||||
await Submit();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
@using Moonlight.App.Helpers.Files
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using Logging.Net
|
||||
|
||||
@inject ToastService ToastService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||
<label for="fileUpload" class="btn btn-primary me-3 @(Uploading ? "disabled" : "")">
|
||||
@if (Uploading)
|
||||
{
|
||||
<span>@(Percent)%</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Upload</TL>
|
||||
}
|
||||
</label>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public FileAccess Access { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task> OnUploadComplete { get; set; }
|
||||
|
||||
private bool Uploading = false;
|
||||
private int Percent = 0;
|
||||
|
||||
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||
{
|
||||
await ToastService.CreateProcessToast("upload", SmartTranslateService.Translate("Uploading files"));
|
||||
|
||||
Uploading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
int i = 1;
|
||||
foreach (var browserFile in arg.GetMultipleFiles())
|
||||
{
|
||||
if (browserFile.Size < 1024 * 1024 * 100)
|
||||
{
|
||||
Percent = 0;
|
||||
|
||||
try
|
||||
{
|
||||
await Access.Upload(
|
||||
browserFile.Name,
|
||||
browserFile.OpenReadStream(1024 * 1024 * 100),
|
||||
async (i) =>
|
||||
{
|
||||
Percent = i;
|
||||
|
||||
Task.Run(() => { InvokeAsync(StateHasChanged); });
|
||||
});
|
||||
|
||||
OnUploadComplete?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while uploading a file"));
|
||||
Logger.Error("Error uploading file");
|
||||
Logger.Error(e);
|
||||
}
|
||||
|
||||
await ToastService.UpdateProcessToast("upload", $"{i}/{arg.GetMultipleFiles().Count} {SmartTranslateService.Translate("complete")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Error(SmartTranslateService.Translate("The uploaded file should not be bigger than 100MB"));
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
Uploading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await ToastService.UpdateProcessToast("upload", SmartTranslateService.Translate("Upload complete"));
|
||||
await ToastService.RemoveProcessToast("upload");
|
||||
}
|
||||
}
|
276
Moonlight/Shared/Components/FileManagerPartials/FileView.razor
Normal file
276
Moonlight/Shared/Components/FileManagerPartials/FileView.razor
Normal file
|
@ -0,0 +1,276 @@
|
|||
@using Moonlight.App.Helpers.Files
|
||||
@using Logging.Net
|
||||
@using BlazorContextMenu
|
||||
@using Moonlight.App.Helpers
|
||||
|
||||
<div class="table-responsive">
|
||||
<div class="dataTables_scroll">
|
||||
<div class="dataTables_scrollHead">
|
||||
<div class="dataTables_scrollHeadInner">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="w-10px pe-2 sorting_disabled">
|
||||
@if (!HideSelect)
|
||||
{
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
|
||||
@if (AllToggled)
|
||||
{
|
||||
<input @onclick="() => SetToggleState(false)" class="form-check-input" type="checkbox" checked="">
|
||||
}
|
||||
else
|
||||
{
|
||||
<input @onclick="() => SetToggleState(true)" class="form-check-input" type="checkbox">
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</th>
|
||||
<th class="min-w-250px sorting_disabled">Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dataTables_scrollBody" style="@(DisableScrolling ? "" : "position: relative; overflow: auto; max-height: 700px; width: 100%;")">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
|
||||
<tbody class="fw-semibold text-gray-600">
|
||||
<LazyLoader Load="Load">
|
||||
<tr class="even">
|
||||
<td class="w-10px">
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="icon-wrapper">
|
||||
<i class="bx bx-md bx-up-arrow-alt text-primary"></i>
|
||||
</span>
|
||||
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
|
||||
<TL>Go up</TL>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="ms-2">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@foreach (var file in Data)
|
||||
{
|
||||
<tr class="even">
|
||||
<td class="w-10px">
|
||||
@if (!HideSelect)
|
||||
{
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
||||
@{
|
||||
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
|
||||
}
|
||||
|
||||
@if (toggle)
|
||||
{
|
||||
<input @onclick="() => SetToggleState(file, false)" class="form-check-input" type="checkbox" checked="checked">
|
||||
}
|
||||
else
|
||||
{
|
||||
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="icon-wrapper">
|
||||
@if (file.IsFile)
|
||||
{
|
||||
<i class="bx bx-md bx-file text-primary"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bx bx-md bx-folder text-primary"></i>
|
||||
}
|
||||
</span>
|
||||
<a href="#" @onclick:preventDefault @onclick="() => OnClicked(file)" class="ms-3 text-gray-800 text-hover-primary">@(file.Name)</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>@(Formatter.FormatSize(file.Size))</td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="ms-2 me-7">
|
||||
@if (ContextActions.Any())
|
||||
{
|
||||
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
|
||||
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
||||
<span class="svg-icon svg-icon-5 m-0">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</LazyLoader>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ContextActions.Any())
|
||||
{
|
||||
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
|
||||
@foreach (var action in ContextActions)
|
||||
{
|
||||
<Item Id="@action.Id" OnClick="OnContextMenuClick">
|
||||
<TL>@action.Name</TL>
|
||||
</Item>
|
||||
}
|
||||
</ContextMenu>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public FileAccess Access { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnSelectionChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ContextAction[] ContextActions { get; set; } = Array.Empty<ContextAction>();
|
||||
|
||||
[Parameter]
|
||||
public bool HideSelect { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public bool DisableScrolling { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public Func<FileData, bool>? Filter { get; set; }
|
||||
|
||||
public FileData[] SelectedFiles => ToggleStatusCache
|
||||
.Where(x => x.Value)
|
||||
.Select(x => x.Key)
|
||||
.ToArray();
|
||||
|
||||
private FileData[] Data = Array.Empty<FileData>();
|
||||
|
||||
private Dictionary<FileData, bool> ToggleStatusCache = new();
|
||||
private bool AllToggled = false;
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
var list = new List<FileData>();
|
||||
|
||||
foreach (var fileData in await Access.Ls())
|
||||
{
|
||||
if (Filter != null)
|
||||
{
|
||||
if(Filter.Invoke(fileData))
|
||||
list.Add(fileData);
|
||||
}
|
||||
else
|
||||
list.Add(fileData);
|
||||
}
|
||||
|
||||
Data = list.ToArray();
|
||||
|
||||
ToggleStatusCache.Clear();
|
||||
AllToggled = false;
|
||||
|
||||
foreach (var fileData in Data)
|
||||
{
|
||||
ToggleStatusCache.Add(fileData, false);
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
OnSelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
{
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private async Task SetToggleState(FileData fileData, bool status)
|
||||
{
|
||||
if (ToggleStatusCache.ContainsKey(fileData))
|
||||
ToggleStatusCache[fileData] = status;
|
||||
else
|
||||
ToggleStatusCache.Add(fileData, status);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
OnSelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private async Task SetToggleState(bool status)
|
||||
{
|
||||
AllToggled = status;
|
||||
|
||||
foreach (var fd in ToggleStatusCache.Keys)
|
||||
{
|
||||
ToggleStatusCache[fd] = status;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
OnSelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private async Task OnClicked(FileData fileData)
|
||||
{
|
||||
if (OnElementClicked != null)
|
||||
{
|
||||
var canceled = await OnElementClicked.Invoke(fileData);
|
||||
|
||||
if (canceled)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileData.IsFile)
|
||||
{
|
||||
await Access.Cd(fileData.Name);
|
||||
await Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GoUp()
|
||||
{
|
||||
if (OnElementClicked != null)
|
||||
{
|
||||
var canceled = await OnElementClicked.Invoke(new()
|
||||
{
|
||||
Name = "..",
|
||||
IsFile = false
|
||||
});
|
||||
|
||||
if (canceled)
|
||||
return;
|
||||
}
|
||||
|
||||
await Access.Up();
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private Task OnContextMenuClick(ItemClickEventArgs eventArgs)
|
||||
{
|
||||
var action = ContextActions.FirstOrDefault(x => x.Id == eventArgs.MenuItem.Id);
|
||||
|
||||
if (action != null)
|
||||
{
|
||||
action.Action.Invoke((FileData)eventArgs.Data);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
57
Moonlight/Shared/Components/Forms/DeleteButton.razor
Normal file
57
Moonlight/Shared/Components/Forms/DeleteButton.razor
Normal file
|
@ -0,0 +1,57 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject AlertService AlertService
|
||||
|
||||
@if (!Working)
|
||||
{
|
||||
<button class="btn btn-danger" @onclick="Do">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-danger disabled" disabled="">
|
||||
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
|
||||
</button>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
private bool Working { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Confirm { get; set; } = false;
|
||||
|
||||
private async Task Do()
|
||||
{
|
||||
Working = true;
|
||||
StateHasChanged();
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
if (Confirm)
|
||||
{
|
||||
var b = await AlertService.YesNo(
|
||||
SmartTranslateService.Translate("Are you sure?"),
|
||||
SmartTranslateService.Translate("Do you really want to delete it?"),
|
||||
SmartTranslateService.Translate("Yes"),
|
||||
SmartTranslateService.Translate("No")
|
||||
);
|
||||
|
||||
if (b)
|
||||
{
|
||||
if(OnClick != null)
|
||||
await OnClick.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Working = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
}
|
102
Moonlight/Shared/Components/Forms/SmartForm.razor
Normal file
102
Moonlight/Shared/Components/Forms/SmartForm.razor
Normal file
|
@ -0,0 +1,102 @@
|
|||
@using Moonlight.App.Helpers
|
||||
|
||||
<div class="form">
|
||||
<EditForm @ref="EditForm" Model="Model" OnValidSubmit="ValidSubmit" OnInvalidSubmit="InvalidSubmit">
|
||||
<DataAnnotationsValidator></DataAnnotationsValidator>
|
||||
@if (Working)
|
||||
{
|
||||
<div class="d-flex flex-center flex-column">
|
||||
<span class="fs-1 spinner-border spinner-border-lg align-middle me-2"></span>
|
||||
<span class="mt-3 fs-5"><TL>Proccessing</TL></span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ErrorMessages.Any())
|
||||
{
|
||||
<div class="alert alert-danger p-10 mb-3">
|
||||
@foreach (var msg in ErrorMessages)
|
||||
{
|
||||
<TL>@(msg)</TL>
|
||||
<br/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@(ChildContent)
|
||||
}
|
||||
</EditForm>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public object Model { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnValidSubmit { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnInvalidSubmit { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnSubmit { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
private EditForm EditForm;
|
||||
|
||||
private List<string> ErrorMessages = new();
|
||||
|
||||
private bool Working = false;
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
EditForm.EditContext!.SetFieldCssClassProvider(new FieldCssHelper());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidSubmit(EditContext context)
|
||||
{
|
||||
ErrorMessages.Clear();
|
||||
Working = true;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
if (OnValidSubmit != null)
|
||||
await OnValidSubmit.Invoke();
|
||||
|
||||
if (OnSubmit != null)
|
||||
await OnSubmit.Invoke();
|
||||
});
|
||||
|
||||
Working = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task InvalidSubmit(EditContext context)
|
||||
{
|
||||
ErrorMessages.Clear();
|
||||
context.Validate();
|
||||
|
||||
foreach (var message in context.GetValidationMessages())
|
||||
{
|
||||
ErrorMessages.Add(message);
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (OnInvalidSubmit != null)
|
||||
await OnInvalidSubmit.Invoke();
|
||||
|
||||
if (OnSubmit != null)
|
||||
await OnSubmit.Invoke();
|
||||
}
|
||||
}
|
59
Moonlight/Shared/Components/Forms/SmartSelect.razor
Normal file
59
Moonlight/Shared/Components/Forms/SmartSelect.razor
Normal file
|
@ -0,0 +1,59 @@
|
|||
@typeparam TField
|
||||
@inherits InputBase<TField>
|
||||
|
||||
<select class="form-select" @bind="Binding">
|
||||
@foreach(var item in Items)
|
||||
{
|
||||
<option value="@(item!.GetHashCode())">@(DisplayField(item))</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public TField[] Items { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<TField, string> DisplayField { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override string? FormatValueAsString(TField? value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
return DisplayField.Invoke(value);
|
||||
}
|
||||
|
||||
protected override bool TryParseValueFromString(string? value, out TField result, out string? validationErrorMessage)
|
||||
{
|
||||
validationErrorMessage = "";
|
||||
result = default(TField)!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int Binding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Value == null)
|
||||
return -1;
|
||||
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
set
|
||||
{
|
||||
var i = Items.FirstOrDefault(x => x!.GetHashCode() == value);
|
||||
|
||||
if (i != null)
|
||||
{
|
||||
Value = i;
|
||||
ValueChanged.InvokeAsync(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,12 +27,17 @@
|
|||
<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="/profile">
|
||||
Overview
|
||||
<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="/profile/subscriptions">
|
||||
Subscriptions
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/profile/security">
|
||||
<TL>Security</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/profile/subscriptions">
|
||||
<TL>Subscriptions</TL>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -9,6 +9,34 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
@inject CookieService CookieService
|
||||
|
||||
<div class="menu menu-column justify-content-center"
|
||||
data-kt-menu="true">
|
||||
<div class="menu-item">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<TL>Create</TL>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item py-2" href="/servers/create">
|
||||
<TL>Server</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item py-2" href="/domains/create">
|
||||
<TL>Domain</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item py-2" href="/websites/create">
|
||||
<TL>Website</TL>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-navbar flex-shrink-0">
|
||||
<div class="app-navbar-item ms-1 ms-lg-3">
|
||||
<ThemeSwitcher
|
||||
|
@ -20,17 +48,11 @@
|
|||
@if (User != null)
|
||||
{
|
||||
<div class="app-navbar-item ms-1 ms-lg-3">
|
||||
<div class="btn btn-icon btn-custom btn-icon-muted btn-active-light btn-active-color-primary w-35px h-35px w-md-40px h-md-40px position-relative" id="support_ticket_toggle">
|
||||
<span class="svg-icon svg-icon-1">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M20 3H4C2.89543 3 2 3.89543 2 5V16C2 17.1046 2.89543 18 4 18H4.5C5.05228 18 5.5 18.4477 5.5 19V21.5052C5.5 22.1441 6.21212 22.5253 6.74376 22.1708L11.4885 19.0077C12.4741 18.3506 13.6321 18 14.8167 18H20C21.1046 18 22 17.1046 22 16V5C22 3.89543 21.1046 3 20 3Z" fill="currentColor"></path>
|
||||
<rect x="6" y="12" width="7" height="2" rx="1" fill="currentColor"></rect>
|
||||
<rect x="6" y="7" width="12" height="2" rx="1" fill="currentColor"></rect>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<a href="/support" class="btn btn-icon btn-custom btn-icon-muted btn-active-light btn-active-color-primary w-35px h-35px w-md-40px h-md-40px position-relative">
|
||||
<i class="bx bx-support"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-navbar-item ms-1 ms-lg-3" id="kt_header_user_menu_toggle">
|
||||
<div class="cursor-pointer symbol symbol-35px symbol-md-40px" data-kt-menu-trigger="click" data-kt-menu-attach="parent" data-kt-menu-placement="bottom-end">
|
||||
<img alt="Avatar" src="/api/moonlight/avatar/@(User.Id)"/>
|
||||
|
@ -45,7 +67,7 @@
|
|||
<div class="d-flex flex-column">
|
||||
<div class="fw-bold d-flex align-items-center fs-5">
|
||||
@(User.FirstName) @(User.LastName)
|
||||
|
||||
|
||||
@if (User.Admin)
|
||||
{
|
||||
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
|
||||
|
@ -57,10 +79,14 @@
|
|||
</div>
|
||||
<div class="separator my-2"></div>
|
||||
<div class="menu-item px-5 my-1">
|
||||
<a href="/profile" class="menu-link px-5"><TL>Profile</TL></a>
|
||||
<a href="/profile" class="menu-link px-5">
|
||||
<TL>Profile</TL>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item px-5">
|
||||
<a @onclick="Logout" class="menu-link px-5"><TL>Logout</TL></a>
|
||||
<a @onclick="Logout" class="menu-link px-5">
|
||||
<TL>Logout</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -86,4 +112,4 @@
|
|||
await CookieService.SetValue("token", "", 1);
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -225,6 +225,14 @@ else
|
|||
<span class="menu-title"><TL>Support</TL></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/admin/subscriptions">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-credit-card"></i>
|
||||
</span>
|
||||
<span class="menu-title"><TL>Subscriptions</TL></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/admin/statistics">
|
||||
<span class="menu-icon">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="alert alert-primary d-flex rounded p-6">
|
||||
<div class="d-flex flex-stack flex-grow-1 flex-wrap flex-md-nowrap">
|
||||
<div class="mb-3 mb-md-0 fw-semibold">
|
||||
<h4 class="text-gray-900 fw-bold"><TL>Plugins</TL></h4>
|
||||
<h4 class="text-gray-900 fw-bold"><TL>Addons</TL></h4>
|
||||
<div class="fs-6 text-gray-700 pe-7">
|
||||
<TL>This feature is currently not available</TL>
|
||||
</div>
|
|
@ -15,21 +15,20 @@
|
|||
@inject AlertService AlertService
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="row g-5 g-xl-10 mb-xl-10">
|
||||
<Terminal @ref="Terminal" RunOnFirstRender="RunOnFirstRender"></Terminal>
|
||||
|
||||
<div class="mt-3 row">
|
||||
<div class="ms-2 input-group">
|
||||
<div class="card card-body rounded bg-black p-3">
|
||||
<div class="d-flex flex-column">
|
||||
<Terminal @ref="Terminal" RunOnFirstRender="RunOnFirstRender"></Terminal>
|
||||
<div class="input-group">
|
||||
<script suppress-error="BL9992">
|
||||
function checkEnter(event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
document.getElementById("sendCmd").click();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<input @bind="@CommandInput" class="form-control" onkeyup="checkEnter(event)" placeholder="@(TranslationService.Translate("Enter command"))"/>
|
||||
|
||||
function checkEnter(event)
|
||||
{
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
document.getElementById("sendCmd").click();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<input @bind="@CommandInput" class="form-control rounded-start" onkeyup="checkEnter(event)" placeholder="@(TranslationService.Translate("Enter command"))"/>
|
||||
<button id="sendCmd" @onclick="SendCommand" class="input-group-text btn btn-primary">@(TranslationService.Translate("Execute"))</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
@using Moonlight.Shared.Components.FileManagerPartials
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Models.Files
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Helpers.Files
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject ServerService ServerService
|
||||
@inject IdentityService IdentityService
|
||||
@inject WingsApiHelper WingsApiHelper
|
||||
@inject WingsJwtHelper WingsJwtHelper
|
||||
@inject ConfigService ConfigService
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<FileManager FileAccess="FileAccess"></FileManager>
|
||||
</LazyLoader>
|
||||
<FileManager Access="FileAccess"></FileManager>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public User User { get; set; }
|
||||
|
||||
private IFileAccess FileAccess;
|
||||
private FileAccess FileAccess;
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var user = await IdentityService.Get(); // User for launch url
|
||||
FileAccess = await ServerService.CreateFileAccess(CurrentServer, user);
|
||||
FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, CurrentServer, ConfigService, User);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,10 +134,10 @@
|
|||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100 me-0 mb-md-2">
|
||||
<a href="/server/@(CurrentServer.Uuid)/plugins" class="nav-link w-100 btn btn-flex @(Index == 4 ? "active" : "") btn-active-light-primary">
|
||||
<a href="/server/@(CurrentServer.Uuid)/addons" class="nav-link w-100 btn btn-flex @(Index == 4 ? "active" : "") btn-active-light-primary">
|
||||
<i class="bx bx-plug bx-sm me-2"></i>
|
||||
<span class="d-flex flex-column align-items-start">
|
||||
<span class="fs-5"><TL>Plugins</TL></span>
|
||||
<span class="fs-5"><TL>Addons</TL></span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -1,51 +1,66 @@
|
|||
@using PteroConsole.NET
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.Shared.Components.ServerControl.Settings
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
|
||||
<div class="row mb-5">
|
||||
@if (Tags.Contains("paperversion"))
|
||||
{
|
||||
<PaperVersionSetting></PaperVersionSetting>
|
||||
}
|
||||
|
||||
@if (Tags.Contains("pythonversion"))
|
||||
{
|
||||
<PythonVersionSetting></PythonVersionSetting>
|
||||
}
|
||||
|
||||
@{
|
||||
/*
|
||||
* @if (Tags.Contains("pythonfile"))
|
||||
{
|
||||
<PythonFileSetting></PythonFileSetting>
|
||||
}
|
||||
|
||||
@if (Tags.Contains("javascriptfile"))
|
||||
{
|
||||
<JavascriptFileSetting></JavascriptFileSetting>
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@if (Tags.Contains("javascriptversion"))
|
||||
{
|
||||
<JavascriptVersionSetting></JavascriptVersionSetting>
|
||||
}
|
||||
|
||||
@if (Tags.Contains("join2start"))
|
||||
{
|
||||
<Join2StartSetting></Join2StartSetting>
|
||||
}
|
||||
</div>
|
||||
<LazyLoader Load="Load">
|
||||
<div class="accordion" id="serverSetting">
|
||||
@foreach (var setting in Settings)
|
||||
{
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="serverSetting-header@(setting.GetHashCode())">
|
||||
<button class="accordion-button fs-4 fw-semibold" type="button" data-bs-toggle="collapse" data-bs-target="#serverSetting-body@(setting.GetHashCode())" aria-expanded="true" aria-controls="serverSetting-body@(setting.GetHashCode())">
|
||||
<TL>@(setting.Key)</TL>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="serverSetting-body@(setting.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverSetting-header@(setting.GetHashCode())" data-bs-parent="#serverSetting">
|
||||
<div class="accordion-body">
|
||||
@(GetComponent(setting.Value))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public PteroConsole Console { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public string[] Tags { get; set; }
|
||||
|
||||
private Dictionary<string, Type> Settings = new();
|
||||
|
||||
private Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
if(Tags.Contains("paperversion"))
|
||||
Settings.Add("Paper version", typeof(PaperVersionSetting));
|
||||
|
||||
if(Tags.Contains("join2start"))
|
||||
Settings.Add("Join2Start", typeof(Join2StartSetting));
|
||||
|
||||
if(Tags.Contains("javascriptversion"))
|
||||
Settings.Add("Javascript version", typeof(JavascriptVersionSetting));
|
||||
|
||||
if(Tags.Contains("javascriptfile"))
|
||||
Settings.Add("Javascript file", typeof(JavascriptFileSetting));
|
||||
|
||||
if(Tags.Contains("pythonversion"))
|
||||
Settings.Add("Python version", typeof(PythonVersionSetting));
|
||||
|
||||
if(Tags.Contains("pythonfile"))
|
||||
Settings.Add("Python file", typeof(PythonFileSetting));
|
||||
|
||||
Settings.Add("Server reset", typeof(ServerResetSetting));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private RenderFragment GetComponent(Type type) => builder =>
|
||||
{
|
||||
builder.OpenComponent(0, type);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Moonlight.Shared.Components.FileManagerPartials
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Helpers.Files
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject WingsApiHelper WingsApiHelper
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<label class="mb-2 form-label">
|
||||
<TL>Javascript file</TL>
|
||||
</label>
|
||||
<input type="text" class="mb-2 form-control disabled" disabled="" value="@(PathAndFile)"/>
|
||||
<button @onclick="Show" class="btn btn-primary"><TL>Change</TL></button>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FileSelectModal @ref="FileSelectModal"
|
||||
Access="Access"
|
||||
Filter="@(x => !x.IsFile || x.Name.EndsWith(".js"))"
|
||||
Title="@(SmartTranslateService.Translate("Select javascript file to execute on start"))"
|
||||
OnlyFolder="false"
|
||||
OnCancel="() => { return Task.CompletedTask; }"
|
||||
OnSubmit="OnSubmit">
|
||||
</FileSelectModal>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private string PathAndFile;
|
||||
private FileAccess Access;
|
||||
|
||||
private FileSelectModal FileSelectModal;
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Access = new WingsFileAccess(WingsApiHelper,
|
||||
null!,
|
||||
CurrentServer,
|
||||
null!,
|
||||
null!
|
||||
);
|
||||
}
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_JS_FILE");
|
||||
|
||||
PathAndFile = v != null ? v.Value : "";
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Show()
|
||||
{
|
||||
await FileSelectModal.Show();
|
||||
}
|
||||
|
||||
private async Task OnSubmit(string path)
|
||||
{
|
||||
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_JS_FILE");
|
||||
|
||||
if (v != null)
|
||||
{
|
||||
v.Value = path.TrimStart("/"[0]);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
}
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
|
@ -8,29 +8,34 @@
|
|||
@inject ServerRepository ServerRepository
|
||||
@inject ImageRepository ImageRepository
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<label class="mb-2 form-label"><TL>Javascript Version</TL></label>
|
||||
<select class="mb-2 form-select" @bind="Image">
|
||||
@foreach (var image in Images)
|
||||
<label class="mb-2 form-label"><TL>Javascript version</TL></label>
|
||||
<select @bind="ImageIndex" class="form-select mb-2">
|
||||
@foreach (var image in DockerImages)
|
||||
{
|
||||
if (image == Image)
|
||||
if (image.Id == SelectedImage.Id)
|
||||
{
|
||||
<option value="@(image)" selected="">@(image)</option>
|
||||
<option value="@(image.Id)" selected="selected">
|
||||
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||
</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(image)">@(image)</option>
|
||||
<option value="@(image.Id)">
|
||||
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||
</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<WButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary"></WButton>
|
||||
<WButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary">
|
||||
</WButton>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,44 +45,36 @@
|
|||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private string[] Images;
|
||||
private string Image;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
private List<DockerImage> DockerImages;
|
||||
private DockerImage SelectedImage;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
private int ImageIndex
|
||||
{
|
||||
//TODO: Check if this is a redundant call
|
||||
var serverImage = ImageRepository
|
||||
get => SelectedImage.Id;
|
||||
set { SelectedImage = DockerImages.First(x => x.Id == value); }
|
||||
}
|
||||
|
||||
private Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
var image = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
|
||||
|
||||
var res = new List<string>();
|
||||
foreach (var image in serverImage.DockerImages)
|
||||
{
|
||||
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
|
||||
}
|
||||
Images = res.ToArray();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
DockerImages = image.DockerImages;
|
||||
|
||||
SelectedImage = DockerImages[CurrentServer.DockerImageIndex];
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
var serverImage = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
var allImages = serverImage.DockerImages;
|
||||
var imageToUse = allImages.First(x => x.Name.EndsWith(Image));
|
||||
CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Logging.Net
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Repositories
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Moonlight.Shared.Components.FileManagerPartials
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Helpers.Files
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject WingsApiHelper WingsApiHelper
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<label class="mb-2 form-label">
|
||||
<TL>Python file</TL>
|
||||
</label>
|
||||
<input type="text" class="mb-2 form-control disabled" disabled="" value="@(PathAndFile)"/>
|
||||
<button @onclick="Show" class="btn btn-primary"><TL>Change</TL></button>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FileSelectModal @ref="FileSelectModal"
|
||||
Access="Access"
|
||||
Filter="@(x => !x.IsFile || x.Name.EndsWith(".py"))"
|
||||
Title="@(SmartTranslateService.Translate("Select python file to execute on start"))"
|
||||
OnlyFolder="false"
|
||||
OnCancel="() => { return Task.CompletedTask; }"
|
||||
OnSubmit="OnSubmit">
|
||||
</FileSelectModal>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private string PathAndFile;
|
||||
private FileAccess Access;
|
||||
|
||||
private FileSelectModal FileSelectModal;
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Access = new WingsFileAccess(WingsApiHelper,
|
||||
null!,
|
||||
CurrentServer,
|
||||
null!,
|
||||
null!
|
||||
);
|
||||
}
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_PY_FILE");
|
||||
|
||||
PathAndFile = v != null ? v.Value : "";
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Show()
|
||||
{
|
||||
await FileSelectModal.Show();
|
||||
}
|
||||
|
||||
private async Task OnSubmit(string path)
|
||||
{
|
||||
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_PY_FILE");
|
||||
|
||||
if (v != null)
|
||||
{
|
||||
v.Value = path.TrimStart("/"[0]);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
}
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
|
@ -15,24 +13,29 @@
|
|||
<div class="card card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<label class="mb-2 form-label"><TL>Python version</TL></label>
|
||||
<select class="mb-2 form-select" @bind="Image">
|
||||
@foreach (var image in Images)
|
||||
<select @bind="ImageIndex" class="form-select mb-2">
|
||||
@foreach (var image in DockerImages)
|
||||
{
|
||||
if (image == Image)
|
||||
if (image.Id == SelectedImage.Id)
|
||||
{
|
||||
<option value="@(image)" selected="">@(image)</option>
|
||||
<option value="@(image.Id)" selected="selected">
|
||||
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||
</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(image)">@(image)</option>
|
||||
<option value="@(image.Id)">
|
||||
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||
</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<WButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary"></WButton>
|
||||
<WButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary">
|
||||
</WButton>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -42,43 +45,36 @@
|
|||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private string[] Images;
|
||||
private string Image;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
private List<DockerImage> DockerImages;
|
||||
private DockerImage SelectedImage;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
private int ImageIndex
|
||||
{
|
||||
var serverImage = ImageRepository
|
||||
get => SelectedImage.Id;
|
||||
set { SelectedImage = DockerImages.First(x => x.Id == value); }
|
||||
}
|
||||
|
||||
private Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
var image = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
|
||||
|
||||
var res = new List<string>();
|
||||
foreach (var image in serverImage.DockerImages)
|
||||
{
|
||||
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
|
||||
}
|
||||
Images = res.ToArray();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
DockerImages = image.DockerImages;
|
||||
|
||||
SelectedImage = DockerImages[CurrentServer.DockerImageIndex];
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
var serverImage = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
var allImages = serverImage.DockerImages;
|
||||
var imageToUse = allImages.First(x => x.Name.EndsWith(Image));
|
||||
CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue