Merge pull request #6 from Moonlight-Panel/main
This commit is contained in:
@@ -41,6 +41,7 @@ public class DataContext : DbContext
|
|||||||
public DbSet<NotificationAction> NotificationActions { get; set; }
|
public DbSet<NotificationAction> NotificationActions { get; set; }
|
||||||
public DbSet<AaPanel> AaPanels { get; set; }
|
public DbSet<AaPanel> AaPanels { get; set; }
|
||||||
public DbSet<Website> Websites { get; set; }
|
public DbSet<Website> Websites { get; set; }
|
||||||
|
public DbSet<DdosAttack> DdosAttacks { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
11
Moonlight/App/Database/Entities/DdosAttack.cs
Normal file
11
Moonlight/App/Database/Entities/DdosAttack.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
public class DdosAttack
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public bool Ongoing { get; set; }
|
||||||
|
public long Data { get; set; }
|
||||||
|
public string Ip { get; set; } = "";
|
||||||
|
public Node Node { get; set; } = null!;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ public class Image
|
|||||||
public string InstallDockerImage { get; set; } = "";
|
public string InstallDockerImage { get; set; } = "";
|
||||||
public string InstallEntrypoint { get; set; } = "";
|
public string InstallEntrypoint { get; set; } = "";
|
||||||
public string Startup { get; set; } = "";
|
public string Startup { get; set; } = "";
|
||||||
|
public int Allocations { get; set; } = 1;
|
||||||
public List<DockerImage> DockerImages { get; set; } = new();
|
public List<DockerImage> DockerImages { get; set; } = new();
|
||||||
public List<ImageVariable> Variables { get; set; } = new();
|
public List<ImageVariable> Variables { get; set; } = new();
|
||||||
public List<ImageTag> Tags { get; set; } = new();
|
public string TagsJson { get; set; } = "";
|
||||||
}
|
}
|
||||||
1023
Moonlight/App/Database/Migrations/20230314164750_SwitchedToJsonTags.Designer.cs
generated
Normal file
1023
Moonlight/App/Database/Migrations/20230314164750_SwitchedToJsonTags.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class SwitchedToJsonTags : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_ImageTags_Images_ImageId",
|
||||||
|
table: "ImageTags");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_ImageTags_ImageId",
|
||||||
|
table: "ImageTags");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ImageId",
|
||||||
|
table: "ImageTags");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "TagsJson",
|
||||||
|
table: "Images",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "InternalAaPanelId",
|
||||||
|
table: "Databases",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Name",
|
||||||
|
table: "Databases",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AaPanels",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Url = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Key = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
BaseDomain = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AaPanels", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Websites",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
InternalAaPanelId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
AaPanelId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
OwnerId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
DomainName = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
PhpVersion = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
FtpUsername = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
FtpPassword = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Websites", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Websites_AaPanels_AaPanelId",
|
||||||
|
column: x => x.AaPanelId,
|
||||||
|
principalTable: "AaPanels",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Websites_Users_OwnerId",
|
||||||
|
column: x => x.OwnerId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Databases_AaPanelId",
|
||||||
|
table: "Databases",
|
||||||
|
column: "AaPanelId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Websites_AaPanelId",
|
||||||
|
table: "Websites",
|
||||||
|
column: "AaPanelId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Websites_OwnerId",
|
||||||
|
table: "Websites",
|
||||||
|
column: "OwnerId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Databases_AaPanels_AaPanelId",
|
||||||
|
table: "Databases",
|
||||||
|
column: "AaPanelId",
|
||||||
|
principalTable: "AaPanels",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Databases_AaPanels_AaPanelId",
|
||||||
|
table: "Databases");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Websites");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AaPanels");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Databases_AaPanelId",
|
||||||
|
table: "Databases");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TagsJson",
|
||||||
|
table: "Images");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "InternalAaPanelId",
|
||||||
|
table: "Databases");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Name",
|
||||||
|
table: "Databases");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ImageId",
|
||||||
|
table: "ImageTags",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ImageTags_ImageId",
|
||||||
|
table: "ImageTags",
|
||||||
|
column: "ImageId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_ImageTags_Images_ImageId",
|
||||||
|
table: "ImageTags",
|
||||||
|
column: "ImageId",
|
||||||
|
principalTable: "Images",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1026
Moonlight/App/Database/Migrations/20230314190100_AddedAllocationsToImage.Designer.cs
generated
Normal file
1026
Moonlight/App/Database/Migrations/20230314190100_AddedAllocationsToImage.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedAllocationsToImage : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Allocations",
|
||||||
|
table: "Images",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Allocations",
|
||||||
|
table: "Images");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1066
Moonlight/App/Database/Migrations/20230320153817_AddedDdosAttacks.Designer.cs
generated
Normal file
1066
Moonlight/App/Database/Migrations/20230320153817_AddedDdosAttacks.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedDdosAttacks : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "DdosAttacks",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Ongoing = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
Data = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
NodeId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_DdosAttacks", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_DdosAttacks_Nodes_NodeId",
|
||||||
|
column: x => x.NodeId,
|
||||||
|
principalTable: "Nodes",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DdosAttacks_NodeId",
|
||||||
|
table: "DdosAttacks",
|
||||||
|
column: "NodeId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "DdosAttacks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,29 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.HasAnnotation("ProductVersion", "7.0.3")
|
.HasAnnotation("ProductVersion", "7.0.3")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -28,16 +51,54 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<int>("AaPanelId")
|
b.Property<int>("AaPanelId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("InternalAaPanelId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<int>("OwnerId")
|
b.Property<int>("OwnerId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AaPanelId");
|
||||||
|
|
||||||
b.HasIndex("OwnerId");
|
b.HasIndex("OwnerId");
|
||||||
|
|
||||||
b.ToTable("Databases");
|
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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -92,6 +153,9 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Allocations")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("ConfigFiles")
|
b.Property<string>("ConfigFiles")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -128,6 +192,10 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("TagsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<Guid>("Uuid")
|
b.Property<Guid>("Uuid")
|
||||||
.HasColumnType("char(36)");
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
@@ -142,17 +210,12 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int?>("ImageId")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("ImageId");
|
|
||||||
|
|
||||||
b.ToTable("ImageTags");
|
b.ToTable("ImageTags");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -726,17 +789,76 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("Users");
|
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 =>
|
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")
|
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("OwnerId")
|
.HasForeignKey("OwnerId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AaPanel");
|
||||||
|
|
||||||
b.Navigation("Owner");
|
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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Moonlight.App.Database.Entities.Image", null)
|
b.HasOne("Moonlight.App.Database.Entities.Image", null)
|
||||||
@@ -763,13 +885,6 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Navigation("SharedDomain");
|
b.Navigation("SharedDomain");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Moonlight.App.Database.Entities.Image", null)
|
|
||||||
.WithMany("Tags")
|
|
||||||
.HasForeignKey("ImageId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Moonlight.App.Database.Entities.Image", null)
|
b.HasOne("Moonlight.App.Database.Entities.Image", null)
|
||||||
@@ -898,12 +1013,29 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Navigation("Subscription");
|
b.Navigation("Subscription");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("DockerImages");
|
b.Navigation("DockerImages");
|
||||||
|
|
||||||
b.Navigation("Tags");
|
|
||||||
|
|
||||||
b.Navigation("Variables");
|
b.Navigation("Variables");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
53
Moonlight/App/Helpers/DaemonApiHelper.cs
Normal file
53
Moonlight/App/Helpers/DaemonApiHelper.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class DaemonApiHelper
|
||||||
|
{
|
||||||
|
private readonly RestClient Client;
|
||||||
|
|
||||||
|
public DaemonApiHelper()
|
||||||
|
{
|
||||||
|
Client = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetApiUrl(Node node)
|
||||||
|
{
|
||||||
|
if(node.Ssl)
|
||||||
|
return $"https://{node.Fqdn}:{node.MoonlightDaemonPort}/";
|
||||||
|
else
|
||||||
|
return $"http://{node.Fqdn}:{node.MoonlightDaemonPort}/";
|
||||||
|
//return $"https://{node.Fqdn}:{node.HttpPort}/";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T> Get<T>(Node node, string resource)
|
||||||
|
{
|
||||||
|
RestRequest request = new(GetApiUrl(node) + resource);
|
||||||
|
|
||||||
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
request.AddHeader("Accept", "application/json");
|
||||||
|
request.AddHeader("Authorization", node.Token);
|
||||||
|
|
||||||
|
var response = await Client.GetAsync(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
if (response.StatusCode != 0)
|
||||||
|
{
|
||||||
|
throw new WingsException(
|
||||||
|
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||||
|
(int)response.StatusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<T>(response.Content!)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,4 +88,25 @@ public static class Formatter
|
|||||||
|
|
||||||
return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year}";
|
return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string FormatSize(double bytes)
|
||||||
|
{
|
||||||
|
var i = Math.Abs(bytes) / 1024D;
|
||||||
|
if (i < 1)
|
||||||
|
{
|
||||||
|
return bytes + " B";
|
||||||
|
}
|
||||||
|
else if (i / 1024D < 1)
|
||||||
|
{
|
||||||
|
return i.Round(2) + " KB";
|
||||||
|
}
|
||||||
|
else if (i / (1024D * 1024D) < 1)
|
||||||
|
{
|
||||||
|
return (i / 1024D).Round(2) + " MB";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (i / (1024D * 1024D)).Round(2) + " GB";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
54
Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs
Normal file
54
Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using Logging.Net;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Http.Requests.Daemon;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Http.Controllers.Api.Remote;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/remote/ddos")]
|
||||||
|
public class DdosController : Controller
|
||||||
|
{
|
||||||
|
private readonly NodeRepository NodeRepository;
|
||||||
|
private readonly MessageService MessageService;
|
||||||
|
private readonly DdosAttackRepository DdosAttackRepository;
|
||||||
|
|
||||||
|
public DdosController(NodeRepository nodeRepository, MessageService messageService, DdosAttackRepository ddosAttackRepository)
|
||||||
|
{
|
||||||
|
NodeRepository = nodeRepository;
|
||||||
|
MessageService = messageService;
|
||||||
|
DdosAttackRepository = ddosAttackRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("update")]
|
||||||
|
public async Task<ActionResult> Update([FromBody] DdosStatus ddosStatus)
|
||||||
|
{
|
||||||
|
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||||
|
var id = tokenData.Split(".")[0];
|
||||||
|
var token = tokenData.Split(".")[1];
|
||||||
|
|
||||||
|
var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id);
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
if (token != node.Token)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var ddosAttack = new DdosAttack()
|
||||||
|
{
|
||||||
|
Ongoing = ddosStatus.Ongoing,
|
||||||
|
Data = ddosStatus.Data,
|
||||||
|
Ip = ddosStatus.Ip,
|
||||||
|
Node = node
|
||||||
|
};
|
||||||
|
|
||||||
|
ddosAttack = DdosAttackRepository.Add(ddosAttack);
|
||||||
|
|
||||||
|
await MessageService.Emit("node.ddos", ddosAttack);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Moonlight/App/Http/Requests/Daemon/DdosStatus.cs
Normal file
8
Moonlight/App/Http/Requests/Daemon/DdosStatus.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Moonlight.App.Http.Requests.Daemon;
|
||||||
|
|
||||||
|
public class DdosStatus
|
||||||
|
{
|
||||||
|
public bool Ongoing { get; set; }
|
||||||
|
public long Data { get; set; }
|
||||||
|
public string Ip { get; set; } = "";
|
||||||
|
}
|
||||||
15
Moonlight/App/Models/Daemon/Resources/ContainerStats.cs
Normal file
15
Moonlight/App/Models/Daemon/Resources/ContainerStats.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Moonlight.App.Models.Daemon.Resources;
|
||||||
|
|
||||||
|
public class ContainerStats
|
||||||
|
{
|
||||||
|
public List<Container> Containers { get; set; } = new();
|
||||||
|
|
||||||
|
public class Container
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public long Memory { get; set; }
|
||||||
|
public double Cpu { get; set; }
|
||||||
|
public long NetworkIn { get; set; }
|
||||||
|
public long NetworkOut { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Moonlight/App/Models/Daemon/Resources/CpuStats.cs
Normal file
8
Moonlight/App/Models/Daemon/Resources/CpuStats.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Moonlight.App.Models.Daemon.Resources;
|
||||||
|
|
||||||
|
public class CpuStats
|
||||||
|
{
|
||||||
|
public double Usage { get; set; }
|
||||||
|
public int Cores { get; set; }
|
||||||
|
public string Model { get; set; } = "";
|
||||||
|
}
|
||||||
9
Moonlight/App/Models/Daemon/Resources/DiskStats.cs
Normal file
9
Moonlight/App/Models/Daemon/Resources/DiskStats.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Moonlight.App.Models.Daemon.Resources;
|
||||||
|
|
||||||
|
public class DiskStats
|
||||||
|
{
|
||||||
|
public long FreeBytes { get; set; }
|
||||||
|
public string DriveFormat { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public long TotalSize { get; set; }
|
||||||
|
}
|
||||||
15
Moonlight/App/Models/Daemon/Resources/MemoryStats.cs
Normal file
15
Moonlight/App/Models/Daemon/Resources/MemoryStats.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Moonlight.App.Models.Daemon.Resources;
|
||||||
|
|
||||||
|
public class MemoryStats
|
||||||
|
{
|
||||||
|
public List<MemoryStick> Sticks { get; set; } = new();
|
||||||
|
public double Free { get; set; }
|
||||||
|
public double Used { get; set; }
|
||||||
|
public double Total { get; set; }
|
||||||
|
|
||||||
|
public class MemoryStick
|
||||||
|
{
|
||||||
|
public int Size { get; set; }
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Moonlight.App.Models.Node;
|
|
||||||
|
|
||||||
public class CpuStats
|
|
||||||
{
|
|
||||||
public double CpuUsage { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Moonlight.App.Models.Node;
|
|
||||||
|
|
||||||
public class DiskStats
|
|
||||||
{
|
|
||||||
public long FreeBytes { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
namespace Moonlight.App.Models.Node;
|
|
||||||
|
|
||||||
public class DockerStats
|
|
||||||
{
|
|
||||||
public ContainerStats[] Containers { get; set; }
|
|
||||||
public int NodeId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ContainerStats
|
|
||||||
{
|
|
||||||
public Guid Name { get; set; }
|
|
||||||
public long Memory { get; set; }
|
|
||||||
public double Cpu { get; set; }
|
|
||||||
public long NetworkIn { get; set; }
|
|
||||||
public long NetworkOut { get; set; }
|
|
||||||
public int NodeId { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Moonlight.App.Models.Node;
|
|
||||||
|
|
||||||
public class MemoryStats
|
|
||||||
{
|
|
||||||
public long Free { get; set; }
|
|
||||||
public long Used { get; set; }
|
|
||||||
public long Total { get; set; }
|
|
||||||
}
|
|
||||||
39
Moonlight/App/Repositories/DdosAttackRepository.cs
Normal file
39
Moonlight/App/Repositories/DdosAttackRepository.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.Database;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
public class DdosAttackRepository
|
||||||
|
{
|
||||||
|
private readonly DataContext DataContext;
|
||||||
|
|
||||||
|
public DdosAttackRepository(DataContext dataContext)
|
||||||
|
{
|
||||||
|
DataContext = dataContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<DdosAttack> Get()
|
||||||
|
{
|
||||||
|
return DataContext.DdosAttacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DdosAttack Add(DdosAttack ddosAttack)
|
||||||
|
{
|
||||||
|
var x = DataContext.DdosAttacks.Add(ddosAttack);
|
||||||
|
DataContext.SaveChanges();
|
||||||
|
return x.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(DdosAttack ddosAttack)
|
||||||
|
{
|
||||||
|
DataContext.DdosAttacks.Update(ddosAttack);
|
||||||
|
DataContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(DdosAttack ddosAttack)
|
||||||
|
{
|
||||||
|
DataContext.DdosAttacks.Remove(ddosAttack);
|
||||||
|
DataContext.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Models.Daemon.Resources;
|
||||||
using Moonlight.App.Models.Wings.Resources;
|
using Moonlight.App.Models.Wings.Resources;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
@@ -7,17 +8,37 @@ namespace Moonlight.App.Services;
|
|||||||
|
|
||||||
public class NodeService
|
public class NodeService
|
||||||
{
|
{
|
||||||
private readonly NodeRepository NodeRepository;
|
|
||||||
private readonly WingsApiHelper WingsApiHelper;
|
private readonly WingsApiHelper WingsApiHelper;
|
||||||
|
private readonly DaemonApiHelper DaemonApiHelper;
|
||||||
|
|
||||||
public NodeService(NodeRepository nodeRepository, WingsApiHelper wingsApiHelper)
|
public NodeService(WingsApiHelper wingsApiHelper, DaemonApiHelper daemonApiHelper)
|
||||||
{
|
{
|
||||||
NodeRepository = nodeRepository;
|
|
||||||
WingsApiHelper = wingsApiHelper;
|
WingsApiHelper = wingsApiHelper;
|
||||||
|
DaemonApiHelper = daemonApiHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SystemStatus> GetStatus(Node node)
|
public async Task<SystemStatus> GetStatus(Node node)
|
||||||
{
|
{
|
||||||
return await WingsApiHelper.Get<SystemStatus>(node, "api/system");
|
return await WingsApiHelper.Get<SystemStatus>(node, "api/system");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<CpuStats> GetCpuStats(Node node)
|
||||||
|
{
|
||||||
|
return await DaemonApiHelper.Get<CpuStats>(node, "stats/cpu");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MemoryStats> GetMemoryStats(Node node)
|
||||||
|
{
|
||||||
|
return await DaemonApiHelper.Get<MemoryStats>(node, "stats/memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DiskStats> GetDiskStats(Node node)
|
||||||
|
{
|
||||||
|
return await DaemonApiHelper.Get<DiskStats>(node, "stats/disk");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ContainerStats> GetContainerStats(Node node)
|
||||||
|
{
|
||||||
|
return await DaemonApiHelper.Get<ContainerStats>(node, "stats/container");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="App\Http\Middleware" />
|
<Folder Include="App\Http\Middleware" />
|
||||||
<Folder Include="App\Models\AuditLogData" />
|
<Folder Include="App\Models\Daemon\Requests" />
|
||||||
<Folder Include="App\Models\Google\Resources" />
|
<Folder Include="App\Models\Google\Resources" />
|
||||||
<Folder Include="App\Services\DiscordBot\Commands" />
|
<Folder Include="App\Services\DiscordBot\Commands" />
|
||||||
<Folder Include="resources\lang" />
|
<Folder Include="resources\lang" />
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<NotificationRepository>();
|
builder.Services.AddScoped<NotificationRepository>();
|
||||||
builder.Services.AddScoped<AaPanelRepository>();
|
builder.Services.AddScoped<AaPanelRepository>();
|
||||||
builder.Services.AddScoped<WebsiteRepository>();
|
builder.Services.AddScoped<WebsiteRepository>();
|
||||||
|
builder.Services.AddScoped<DdosAttackRepository>();
|
||||||
|
|
||||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
builder.Services.AddScoped<AuditLogEntryRepository>();
|
||||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
||||||
@@ -118,6 +119,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<WingsConsoleHelper>();
|
builder.Services.AddScoped<WingsConsoleHelper>();
|
||||||
builder.Services.AddSingleton<PaperApiHelper>();
|
builder.Services.AddSingleton<PaperApiHelper>();
|
||||||
builder.Services.AddSingleton<HostSystemHelper>();
|
builder.Services.AddSingleton<HostSystemHelper>();
|
||||||
|
builder.Services.AddScoped<DaemonApiHelper>();
|
||||||
|
|
||||||
// Background services
|
// Background services
|
||||||
builder.Services.AddSingleton<DiscordBotService>();
|
builder.Services.AddSingleton<DiscordBotService>();
|
||||||
@@ -152,7 +154,7 @@ namespace Moonlight
|
|||||||
var supportServerService = app.Services.GetRequiredService<SupportServerService>();
|
var supportServerService = app.Services.GetRequiredService<SupportServerService>();
|
||||||
|
|
||||||
// Discord bot service
|
// Discord bot service
|
||||||
var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
|
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
|
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (!HideControls)
|
||||||
|
{
|
||||||
<div class="card-footer pt-0">
|
<div class="card-footer pt-0">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<WButton
|
<WButton
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
OnClick="Cancel"></WButton>
|
OnClick="Cancel"></WButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
@@ -30,6 +33,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Language { get; set; }
|
public string Language { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool HideControls { get; set; } = false;
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Action<string> OnSubmit { get; set; }
|
public Action<string> OnSubmit { get; set; }
|
||||||
@@ -95,4 +101,9 @@
|
|||||||
{
|
{
|
||||||
await InvokeAsync(() => OnCancel?.Invoke());
|
await InvokeAsync(() => OnCancel?.Invoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetData()
|
||||||
|
{
|
||||||
|
return await Editor.GetValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<div class="card mb-5 mb-xl-10">
|
||||||
|
<div class="card-body pt-0 pb-0">
|
||||||
|
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/nodes">
|
||||||
|
<TL>Nodes</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/nodes/ddos">
|
||||||
|
<TL>DDos</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public int Index { get; set; } = 0;
|
||||||
|
}
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||||
<li class="nav-item mt-2">
|
<li class="nav-item mt-2">
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/users">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/users">
|
||||||
Users
|
<TL>Users</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item mt-2">
|
<li class="nav-item mt-2">
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/users/sessions">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/users/sessions">
|
||||||
Sessions
|
<TL>Sessions</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ else
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
<a class="menu-link" href="/admin/images">
|
<a class="menu-link" href="/admin/servers/images">
|
||||||
<span class="menu-bullet">
|
<span class="menu-bullet">
|
||||||
<span class="bullet bullet-dot"></span>
|
<span class="bullet bullet-dot"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
100
Moonlight/Shared/Views/Admin/Nodes/Ddos.razor
Normal file
100
Moonlight/Shared/Views/Admin/Nodes/Ddos.razor
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
@page "/admin/nodes/ddos"
|
||||||
|
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using BlazorTable
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
@inject DdosAttackRepository DdosAttackRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject MessageService MessageService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<AdminNodesNavigation Index="1"/>
|
||||||
|
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body pt-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="DdosAttack" Items="DdosAttacks" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||||
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
@if (context.Ongoing)
|
||||||
|
{
|
||||||
|
<TL>DDos attack started</TL>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TL>DDos attack stopped</TL>
|
||||||
|
}
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Node"))" Field="@(x => x.Node)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
|
<a href="/admin/nodes/view/@(context.Id)">
|
||||||
|
@(context.Node.Name)
|
||||||
|
</a>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="DdosAttack" Title="Ip" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/>
|
||||||
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
@if (context.Ongoing)
|
||||||
|
{
|
||||||
|
@(context.Data)
|
||||||
|
<TL> packets</TL>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@(context.Data)
|
||||||
|
<span> MB</span>
|
||||||
|
}
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Date"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
@(Formatter.FormatDate(context.CreatedAt))
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private DdosAttack[] DdosAttacks;
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
|
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
MessageService.Subscribe<Ddos, DdosAttack>("node.ddos", this, async attack => { Task.Run(async () => await LazyLoader.Reload()); });
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
DdosAttacks = DdosAttackRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Node)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
MessageService.Unsubscribe("node.ddos", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
@page "/admin/nodes"
|
@page "/admin/nodes"
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Helpers
|
|
||||||
@using Moonlight.App.Models.Node
|
|
||||||
@using Moonlight.App.Models.Wings.Resources
|
@using Moonlight.App.Models.Wings.Resources
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Logging.Net
|
@using Logging.Net
|
||||||
|
@using BlazorTable
|
||||||
|
|
||||||
@inject NodeRepository NodeRepository
|
@inject NodeRepository NodeRepository
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@@ -14,47 +14,33 @@
|
|||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<div class="row mb-5">
|
<AdminNodesNavigation Index="0"/>
|
||||||
<div class="card card-body">
|
|
||||||
<a class="btn btn-primary" href="/admin/nodes/new">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<TL>Add a new node</TL>
|
<div class="card">
|
||||||
|
<div class="card-header border-0 pt-5">
|
||||||
|
<h3 class="card-title align-items-start flex-column">
|
||||||
|
<span class="card-label fw-bold fs-3 mb-1">
|
||||||
|
<TL>Nodes</TL>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="card-toolbar">
|
||||||
|
<a href="/admin/nodes/new" class="btn btn-sm btn-light-success">
|
||||||
|
<i class="bx bx-layer-plus"></i>
|
||||||
|
<TL>New node</TL>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="card-body pt-0">
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
@if (Nodes.Any())
|
||||||
@if (!Nodes.Any())
|
|
||||||
{
|
{
|
||||||
<div class="card card-body">
|
<div class="table-responsive">
|
||||||
<div class="alert alert-info">
|
<Table TableItem="Node" Items="Nodes" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
<TL>No nodes found. Start with adding a new node</TL>
|
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||||
</div>
|
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="true" Filterable="true">
|
||||||
</div>
|
<Template>
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var node in Nodes)
|
|
||||||
{
|
|
||||||
<div class="col-xl-6 mb-xl-10">
|
|
||||||
<div class="card card-flush h-xl-100">
|
|
||||||
<div class="card-header pt-5">
|
|
||||||
<h4 class="card-title d-flex align-items-start flex-column">
|
|
||||||
<span class="card-label fw-bold text-gray-800">@(node.Name) - (ID: @(node.Id))</span>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body pt-6">
|
|
||||||
<div class="d-flex flex-stack">
|
|
||||||
<div class="d-flex align-items-center me-3">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<span class="text-gray-800 fs-5 fw-bold lh-0">
|
|
||||||
<TL>Status</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center w-100 mw-125px">
|
|
||||||
<span class="text-white-400 fw-semibold">
|
|
||||||
@{
|
@{
|
||||||
var ss = StatusCache.ContainsKey(node) ? StatusCache[node] : null;
|
var ss = StatusCache.ContainsKey(context) ? StatusCache[context] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (ss == null)
|
@if (ss == null)
|
||||||
@@ -65,119 +51,50 @@
|
|||||||
{
|
{
|
||||||
<span class="text-success">Online (@(ss.Version))</span>
|
<span class="text-success">Online (@(ss.Version))</span>
|
||||||
}
|
}
|
||||||
</span>
|
</Template>
|
||||||
</div>
|
</Column>
|
||||||
</div>
|
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||||
<div class="separator separator-dashed my-3"></div>
|
<Template>
|
||||||
<div class="d-flex flex-stack">
|
<a href="/admin/nodes/view/@(context.Id)">@(context.Name)</a>
|
||||||
<div class="d-flex align-items-center me-3">
|
</Template>
|
||||||
<div class="flex-grow-1">
|
</Column>
|
||||||
<span class="text-gray-800 fs-5 fw-bold lh-0">
|
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Fqdn"))" Field="@(x => x.Fqdn)" Sortable="true" Filterable="true"/>
|
||||||
<TL>CPU Usage</TL>
|
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
</span>
|
<Template>
|
||||||
<span class="text-gray-400 fw-semibold d-block fs-6">
|
<a href="/admin/nodes/edit/@(context.Id)">
|
||||||
<TL>In %</TL>
|
@(SmartTranslateService.Translate("Edit"))
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center w-100 mw-125px">
|
|
||||||
<span class="text-white-400 fw-semibold">
|
|
||||||
@{
|
|
||||||
var cpu = CpuCache.ContainsKey(node) ? CpuCache[node] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (cpu == null)
|
|
||||||
{
|
|
||||||
<span>Loading</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>@(cpu.CpuUsage)%</span>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator separator-dashed my-3"></div>
|
|
||||||
<div class="d-flex flex-stack">
|
|
||||||
<div class="d-flex align-items-center me-3">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<span class="text-gray-800 fs-5 fw-bold lh-0">
|
|
||||||
<TL>Memory</TL>
|
|
||||||
</span>
|
|
||||||
<span class="text-gray-400 fw-semibold d-block fs-6">
|
|
||||||
<TL>Used / Available memory</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center w-100 mw-125px">
|
|
||||||
<span class="text-white-400 fw-semibold">
|
|
||||||
@{
|
|
||||||
var memory = MemoryCache.ContainsKey(node) ? MemoryCache[node] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (memory == null)
|
|
||||||
{
|
|
||||||
<span>Loading</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>@(Formatter.FormatSize(memory.Total - memory.Free)) / @(Formatter.FormatSize(memory.Total))</span>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator separator-dashed my-3"></div>
|
|
||||||
<div class="d-flex flex-stack">
|
|
||||||
<div class="d-flex align-items-center me-3">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<span class="text-gray-800 fs-5 fw-bold lh-0">
|
|
||||||
<TL>Storage</TL>
|
|
||||||
</span>
|
|
||||||
<span class="text-gray-400 fw-semibold d-block fs-6">
|
|
||||||
<TL>Available storage</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center w-100 mw-125px">
|
|
||||||
<span class="text-white-400 fw-semibold">
|
|
||||||
@{
|
|
||||||
var disk = DiskCache.ContainsKey(node) ? DiskCache[node] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (disk == null)
|
|
||||||
{
|
|
||||||
<span>Loading</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>@(Formatter.FormatSize(disk.FreeBytes))</span>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-5"></div>
|
|
||||||
<div class="d-flex flex-stack">
|
|
||||||
<div class="align-items-start">
|
|
||||||
<a class="btn btn-primary" href="/admin/nodes/edit/@(node.Id)">
|
|
||||||
<TL>Edit</TL>
|
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-success" href="/admin/nodes/setup/@(node.Id)">
|
</Template>
|
||||||
<TL>Setup</TL>
|
</Column>
|
||||||
|
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
|
<a href="/admin/nodes/setup/@(context.Id)">
|
||||||
|
@(SmartTranslateService.Translate("Setup"))
|
||||||
</a>
|
</a>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||||
CssClasses="btn-danger"
|
CssClasses="btn-sm btn-danger"
|
||||||
OnClick="() => Delete(node)">
|
OnClick="() => Delete(context)">
|
||||||
</WButton>
|
</WButton>
|
||||||
</div>
|
</Template>
|
||||||
</div>
|
</Column>
|
||||||
</div>
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
</div>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<TL>No nodes found</TL>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</div>
|
|
||||||
</OnlyAdmin>
|
</OnlyAdmin>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
@@ -186,17 +103,10 @@
|
|||||||
|
|
||||||
private LazyLoader LazyLoader;
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
private Dictionary<Node, CpuStats> CpuCache = new();
|
|
||||||
private Dictionary<Node, MemoryStats> MemoryCache = new();
|
|
||||||
private Dictionary<Node, DiskStats> DiskCache = new();
|
|
||||||
private Dictionary<Node, SystemStatus?> StatusCache = new();
|
private Dictionary<Node, SystemStatus?> StatusCache = new();
|
||||||
|
|
||||||
private Task Load(LazyLoader lazyLoader)
|
private Task Load(LazyLoader lazyLoader)
|
||||||
{
|
{
|
||||||
CpuCache.Clear();
|
|
||||||
MemoryCache.Clear();
|
|
||||||
DiskCache.Clear();
|
|
||||||
|
|
||||||
lock (StatusCache)
|
lock (StatusCache)
|
||||||
{
|
{
|
||||||
StatusCache.Clear();
|
StatusCache.Clear();
|
||||||
@@ -222,11 +132,6 @@
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Debug(e.Message);
|
Logger.Debug(e.Message);
|
||||||
|
|
||||||
lock (StatusCache)
|
|
||||||
{
|
|
||||||
StatusCache.Add(node, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|||||||
357
Moonlight/Shared/Views/Admin/Nodes/View.razor
Normal file
357
Moonlight/Shared/Views/Admin/Nodes/View.razor
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
@page "/admin/nodes/view/{id:int}"
|
||||||
|
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Models.Daemon.Resources
|
||||||
|
@using Moonlight.App.Models.Wings.Resources
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
|
@inject NodeRepository NodeRepository
|
||||||
|
@inject NodeService NodeService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
@if (Node == null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<TL>No node with this id found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-center">
|
||||||
|
<div class="row">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">
|
||||||
|
<span class="fw-bold fs-3">
|
||||||
|
@(Node.Name) <TL>details</TL>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row g-3 g-lg-6">
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
|
||||||
|
<div class="symbol symbol-30px me-5 mb-8">
|
||||||
|
<span class="symbol-label">
|
||||||
|
<i class="text-primary bx bx-lg bx-chip"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-0">
|
||||||
|
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
|
||||||
|
@if (CpuStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>
|
||||||
|
@(CpuStats.Usage)% <TL>of</TL> @(CpuStats.Cores) <TL>Cores used</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fw-semibold fs-6">
|
||||||
|
@if (CpuStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@(CpuStats.Model)</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
|
||||||
|
<div class="symbol symbol-30px me-5 mb-8">
|
||||||
|
<span class="symbol-label">
|
||||||
|
<i class="text-primary bx bx-lg bx-microchip"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-0">
|
||||||
|
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
|
||||||
|
@if (MemoryStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>
|
||||||
|
@(Formatter.FormatSize(MemoryStats.Used * 1024D * 1024D)) <TL>of</TL> @(Formatter.FormatSize(MemoryStats.Total * 1024D * 1024D)) <TL>used</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fw-semibold fs-6">
|
||||||
|
@if (MemoryStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (MemoryStats.Sticks.Any())
|
||||||
|
{
|
||||||
|
foreach (var stick in SortMemorySticks(MemoryStats.Sticks))
|
||||||
|
{
|
||||||
|
<span>@(stick)</span>
|
||||||
|
<br/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>No memory sticks detected</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
|
||||||
|
<div class="symbol symbol-30px me-5 mb-8">
|
||||||
|
<span class="symbol-label">
|
||||||
|
<i class="text-primary bx bx-lg bx-microchip"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-0">
|
||||||
|
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
|
||||||
|
@if (DiskStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>
|
||||||
|
@(Formatter.FormatSize(DiskStats.TotalSize - DiskStats.FreeBytes)) <TL>of</TL> @(Formatter.FormatSize(DiskStats.TotalSize)) <TL>used</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fw-semibold fs-6">
|
||||||
|
@if (DiskStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@(DiskStats.Name) - @(DiskStats.DriveFormat)</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 row g-3 g-lg-6">
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
|
||||||
|
<div class="symbol symbol-30px me-5 mb-8">
|
||||||
|
<span class="symbol-label">
|
||||||
|
<i class="text-primary bx bx-lg bx-purchase-tag"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-0">
|
||||||
|
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
|
||||||
|
@if (SystemStatus == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-success">
|
||||||
|
<TL>Online</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fw-semibold fs-6">
|
||||||
|
@if (SystemStatus == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@(SystemStatus.Version)</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
|
||||||
|
<div class="symbol symbol-30px me-5 mb-8">
|
||||||
|
<span class="symbol-label">
|
||||||
|
<i class="text-primary bx bx-lg bx-fingerprint"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-0">
|
||||||
|
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
|
||||||
|
@if (SystemStatus == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>
|
||||||
|
@(SystemStatus.KernelVersion) - @(SystemStatus.Architecture)
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fw-semibold fs-6">
|
||||||
|
@if (SystemStatus == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TL>Host system information</TL>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
|
||||||
|
<div class="symbol symbol-30px me-5 mb-8">
|
||||||
|
<span class="symbol-label">
|
||||||
|
<i class="text-primary bx bx-lg bxl-docker"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-0">
|
||||||
|
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
|
||||||
|
@if (ContainerStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>
|
||||||
|
<TL>@(ContainerStats.Containers.Count)</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fw-semibold fs-6">
|
||||||
|
@if (ContainerStats == null)
|
||||||
|
{
|
||||||
|
<span class="text-muted">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TL>Docker containers running</TL>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 card card-body">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<a href="/admin/nodes" class="btn btn-primary">
|
||||||
|
<TL>Cancel</TL>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</LazyLoader>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
private Node? Node;
|
||||||
|
|
||||||
|
private CpuStats CpuStats;
|
||||||
|
private MemoryStats MemoryStats;
|
||||||
|
private DiskStats DiskStats;
|
||||||
|
private SystemStatus SystemStatus;
|
||||||
|
private ContainerStats ContainerStats;
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
Node = NodeRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.Id == Id);
|
||||||
|
|
||||||
|
if (Node != null)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SystemStatus = await NodeService.GetStatus(Node);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
CpuStats = await NodeService.GetCpuStats(Node);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
MemoryStats = await NodeService.GetMemoryStats(Node);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
DiskStats = await NodeService.GetDiskStats(Node);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
ContainerStats = await NodeService.GetContainerStats(Node);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> SortMemorySticks(List<MemoryStats.MemoryStick> sticks)
|
||||||
|
{
|
||||||
|
// Thank you ChatGPT <3
|
||||||
|
|
||||||
|
var groupedMemory = sticks.GroupBy(memory => new { memory.Type, memory.Size })
|
||||||
|
.Select(group => new
|
||||||
|
{
|
||||||
|
Type = group.Key.Type,
|
||||||
|
Size = group.Key.Size,
|
||||||
|
Count = group.Count()
|
||||||
|
});
|
||||||
|
|
||||||
|
var sortedMemory = groupedMemory.OrderBy(memory => memory.Type)
|
||||||
|
.ThenBy(memory => memory.Size);
|
||||||
|
|
||||||
|
List<string> sortedList = sortedMemory.Select(memory =>
|
||||||
|
{
|
||||||
|
string sizeString = $"{memory.Size}GB";
|
||||||
|
return $"{memory.Count}x {memory.Type} {sizeString}";
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return sortedList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,8 +141,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<div class="btn-group">
|
<div class="d-flex justify-content-end">
|
||||||
<a class="btn btn-primary" href="/admin/servers">Back</a>
|
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||||
|
<TL>Cancel</TL>
|
||||||
|
</a>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||||
CssClasses="btn-success"
|
CssClasses="btn-success"
|
||||||
|
|||||||
353
Moonlight/Shared/Views/Admin/Servers/Images/Edit.razor
Normal file
353
Moonlight/Shared/Views/Admin/Servers/Images/Edit.razor
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
@page "/admin/servers/images/edit/{Id:int}"
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.Shared.Components.FileManagerPartials
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
|
||||||
|
@inject ImageRepository ImageRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<div class="row">
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
@if (Image == null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<TL>No image with this id found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Name</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.Name" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Description</TL>
|
||||||
|
</label>
|
||||||
|
<textarea @bind="Image.Description" type="text" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Tags</TL>
|
||||||
|
</label>
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
<input @bind="AddTagName" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter tag name"))">
|
||||||
|
<button @onclick="AddTag" class="btn btn-primary">
|
||||||
|
<TL>Add</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (Tags.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var tag in Tags)
|
||||||
|
{
|
||||||
|
<button @onclick="() => RemoveTag(tag)" class="col m-3 btn btn-outline-primary mw-25">
|
||||||
|
@(tag)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-primary">
|
||||||
|
<TL>No tags found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Docker images</TL>
|
||||||
|
</label>
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
<input @bind="NewDockerImage.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter docker image name"))">
|
||||||
|
<button @onclick="AddDockerImage" class="btn btn-primary">
|
||||||
|
<TL>Add</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (Image.DockerImages.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var imageDocker in Image.DockerImages)
|
||||||
|
{
|
||||||
|
<button @onclick="() => RemoveDockerImage(imageDocker)" class="col m-3 btn btn-outline-primary mw-25">
|
||||||
|
@(imageDocker.Name)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-primary">
|
||||||
|
<TL>No docker images found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Default image</TL>
|
||||||
|
</label>
|
||||||
|
<select @bind="DefaultImageIndex" class="form-select">
|
||||||
|
@foreach (var image in Image.DockerImages)
|
||||||
|
{
|
||||||
|
<option value="@(image.Id)">@(image.Name)</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Allocations</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.Allocations" type="number" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mx-0">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Startup command</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.Startup" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Install container</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.InstallDockerImage" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Install entry</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.InstallEntrypoint" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-flush">
|
||||||
|
<FileEditor @ref="Editor" Language="shell" InitialData="@(Image.InstallScript)" HideControls="true"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-8">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Configuration files</TL>
|
||||||
|
</label>
|
||||||
|
<textarea @bind="Image.ConfigFiles" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Startup detection</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.StartupDetection" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Stop command</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.StopCommand" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-6">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
<input type="text" @bind="ImageVariable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||||
|
<input type="text" @bind="ImageVariable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||||
|
<button @onclick="AddVariable" class="btn btn-primary">
|
||||||
|
<TL>Add</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
@if (Image!.Variables.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var variable in Image!.Variables)
|
||||||
|
{
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" @bind="variable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||||
|
<input type="text" @bind="variable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||||
|
<button @onclick="() => RemoveVariable(variable)" class="btn btn-danger">
|
||||||
|
<TL>Remove</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-primary">
|
||||||
|
<TL>No variables found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||||
|
<TL>Cancel</TL>
|
||||||
|
</a>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||||
|
CssClasses="btn-success"
|
||||||
|
OnClick="Save">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
private Image? Image;
|
||||||
|
|
||||||
|
private List<string> Tags;
|
||||||
|
private string AddTagName = "";
|
||||||
|
|
||||||
|
private DockerImage NewDockerImage = new();
|
||||||
|
private ImageVariable ImageVariable = new();
|
||||||
|
|
||||||
|
private FileEditor Editor;
|
||||||
|
|
||||||
|
private int DefaultImageIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var i = Image.DockerImages.FirstOrDefault(x => x.Default);
|
||||||
|
return i?.Id ?? -1;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (var image in Image!.DockerImages)
|
||||||
|
{
|
||||||
|
image.Default = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = Image.DockerImages.FirstOrDefault(x => x.Id == value);
|
||||||
|
|
||||||
|
if (i != null)
|
||||||
|
i.Default = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
Image = ImageRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Variables)
|
||||||
|
.Include(x => x.DockerImages)
|
||||||
|
.FirstOrDefault(x => x.Id == Id);
|
||||||
|
|
||||||
|
if (Image != null)
|
||||||
|
{
|
||||||
|
Tags = new();
|
||||||
|
|
||||||
|
foreach (var tag in JsonConvert.DeserializeObject<string[]>(Image.TagsJson) ?? Array.Empty<string>())
|
||||||
|
{
|
||||||
|
Tags.Add(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddTag()
|
||||||
|
{
|
||||||
|
Tags.Add(AddTagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveTag(string tag)
|
||||||
|
{
|
||||||
|
Tags.Remove(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddDockerImage()
|
||||||
|
{
|
||||||
|
Image!.DockerImages.Add(NewDockerImage);
|
||||||
|
NewDockerImage = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveDockerImage(DockerImage image)
|
||||||
|
{
|
||||||
|
Image!.DockerImages.Remove(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddVariable()
|
||||||
|
{
|
||||||
|
Image!.Variables.Add(ImageVariable);
|
||||||
|
ImageVariable = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveVariable(ImageVariable variable)
|
||||||
|
{
|
||||||
|
Image!.Variables.Remove(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
if (Image == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Image.TagsJson = JsonConvert.SerializeObject(Tags);
|
||||||
|
Image.InstallScript = await Editor.GetData();
|
||||||
|
|
||||||
|
ImageRepository.Update(Image);
|
||||||
|
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Successfully saved image"));
|
||||||
|
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
91
Moonlight/Shared/Views/Admin/Servers/Images/Index.razor
Normal file
91
Moonlight/Shared/Views/Admin/Servers/Images/Index.razor
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
@page "/admin/servers/images"
|
||||||
|
|
||||||
|
@using BlazorTable
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
|
@inject ImageRepository ImageRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<div class="row">
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header border-0 pt-5">
|
||||||
|
<h3 class="card-title align-items-start flex-column">
|
||||||
|
<span class="card-label fw-bold fs-3 mb-1">
|
||||||
|
<TL>Images</TL>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="card-toolbar">
|
||||||
|
<a href="/admin/servers/images/new" class="btn btn-sm btn-light-success">
|
||||||
|
<i class="bx bx-layer-plus"></i>
|
||||||
|
<TL>New image</TL>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-0">
|
||||||
|
@if (Images.Any())
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="Image" Items="Images" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||||
|
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||||
|
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => x.Description)" Sortable="true" Filterable="true"/>
|
||||||
|
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Uuid"))" Field="@(x => x.Uuid)" Sortable="true" Filterable="true"/>
|
||||||
|
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
|
<a href="/admin/servers/images/edit/@(context.Id)">
|
||||||
|
@(SmartTranslateService.Translate("Edit"))
|
||||||
|
</a>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="() => Delete(context)">
|
||||||
|
</WButton>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<TL>No images found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private Image[] Images;
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
Images = ImageRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.DockerImages)
|
||||||
|
.Include(x => x.Variables)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Delete(Image image)
|
||||||
|
{
|
||||||
|
ImageRepository.Delete(image);
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
340
Moonlight/Shared/Views/Admin/Servers/Images/New.razor
Normal file
340
Moonlight/Shared/Views/Admin/Servers/Images/New.razor
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
@page "/admin/servers/images/new"
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.Shared.Components.FileManagerPartials
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
|
||||||
|
@inject ImageRepository ImageRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ToastService ToastService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<div class="row">
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Name</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.Name" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Description</TL>
|
||||||
|
</label>
|
||||||
|
<textarea @bind="Image.Description" type="text" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Tags</TL>
|
||||||
|
</label>
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
<input @bind="AddTagName" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter tag name"))">
|
||||||
|
<button @onclick="AddTag" class="btn btn-primary">
|
||||||
|
<TL>Add</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (Tags.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var tag in Tags)
|
||||||
|
{
|
||||||
|
<button @onclick="() => RemoveTag(tag)" class="col m-3 btn btn-outline-primary mw-25">
|
||||||
|
@(tag)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-primary">
|
||||||
|
<TL>No tags found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Docker images</TL>
|
||||||
|
</label>
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
<input @bind="NewDockerImage.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter docker image name"))">
|
||||||
|
<button @onclick="AddDockerImage" class="btn btn-primary">
|
||||||
|
<TL>Add</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (Image.DockerImages.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var imageDocker in Image.DockerImages)
|
||||||
|
{
|
||||||
|
<button @onclick="() => RemoveDockerImage(imageDocker)" class="col m-3 btn btn-outline-primary mw-25">
|
||||||
|
@(imageDocker.Name)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-primary">
|
||||||
|
<TL>No docker images found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Default image</TL>
|
||||||
|
</label>
|
||||||
|
<select @bind="DefaultImageIndex" class="form-select">
|
||||||
|
@foreach (var image in Image.DockerImages)
|
||||||
|
{
|
||||||
|
<option value="@(image.Id)">@(image.Name)</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Allocations</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.Allocations" type="number" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mx-0">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Startup command</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.Startup" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Install container</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.InstallDockerImage" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Install entry</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.InstallEntrypoint" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-flush">
|
||||||
|
<FileEditor @ref="Editor" Language="shell" InitialData="@(Image.InstallScript)" HideControls="true"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-8">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Configuration files</TL>
|
||||||
|
</label>
|
||||||
|
<textarea @bind="Image.ConfigFiles" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Startup detection</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.StartupDetection" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Stop command</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="Image.StopCommand" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-6">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
<input type="text" @bind="ImageVariable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||||
|
<input type="text" @bind="ImageVariable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||||
|
<button @onclick="AddVariable" class="btn btn-primary">
|
||||||
|
<TL>Add</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
@if (Image!.Variables.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var variable in Image!.Variables)
|
||||||
|
{
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" @bind="variable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||||
|
<input type="text" @bind="variable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||||
|
<button @onclick="() => RemoveVariable(variable)" class="btn btn-danger">
|
||||||
|
<TL>Remove</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-primary">
|
||||||
|
<TL>No variables found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||||
|
<TL>Cancel</TL>
|
||||||
|
</a>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||||
|
CssClasses="btn-success"
|
||||||
|
OnClick="Save">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private Image Image = new()
|
||||||
|
{
|
||||||
|
Variables = new(),
|
||||||
|
DockerImages = new(),
|
||||||
|
InstallScript = "# install script here",
|
||||||
|
Name = "Name",
|
||||||
|
TagsJson = "[]",
|
||||||
|
Description = "Description",
|
||||||
|
Startup = "",
|
||||||
|
Uuid = Guid.NewGuid(),
|
||||||
|
ConfigFiles = "{}",
|
||||||
|
InstallEntrypoint = "ash",
|
||||||
|
StartupDetection = "Done",
|
||||||
|
StopCommand = "^C",
|
||||||
|
InstallDockerImage = "ghcr.io/pterodactyl/installers:alpine"
|
||||||
|
};
|
||||||
|
|
||||||
|
private List<string> Tags;
|
||||||
|
private string AddTagName = "";
|
||||||
|
|
||||||
|
private DockerImage NewDockerImage = new();
|
||||||
|
private ImageVariable ImageVariable = new();
|
||||||
|
|
||||||
|
private FileEditor Editor;
|
||||||
|
|
||||||
|
private int DefaultImageIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var i = Image.DockerImages.FirstOrDefault(x => x.Default);
|
||||||
|
return i?.Id ?? -1;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (var image in Image!.DockerImages)
|
||||||
|
{
|
||||||
|
image.Default = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = Image.DockerImages.FirstOrDefault(x => x.Id == value);
|
||||||
|
|
||||||
|
if (i != null)
|
||||||
|
i.Default = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
Tags = new();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddTag()
|
||||||
|
{
|
||||||
|
Tags.Add(AddTagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveTag(string tag)
|
||||||
|
{
|
||||||
|
Tags.Remove(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddDockerImage()
|
||||||
|
{
|
||||||
|
Image!.DockerImages.Add(NewDockerImage);
|
||||||
|
NewDockerImage = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveDockerImage(DockerImage image)
|
||||||
|
{
|
||||||
|
Image!.DockerImages.Remove(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddVariable()
|
||||||
|
{
|
||||||
|
Image!.Variables.Add(ImageVariable);
|
||||||
|
ImageVariable = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveVariable(ImageVariable variable)
|
||||||
|
{
|
||||||
|
Image!.Variables.Remove(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
if (Image == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Image.TagsJson = JsonConvert.SerializeObject(Tags);
|
||||||
|
Image.InstallScript = await Editor.GetData();
|
||||||
|
|
||||||
|
ImageRepository.Add(Image);
|
||||||
|
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Successfully added image"));
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo("/admin/servers/images");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,11 @@
|
|||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<Table TableItem="Server" Items="Servers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
<Table TableItem="Server" Items="Servers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
<a href="/server/@(context.Uuid)">@(context.Name)</a>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true"/>
|
||||||
|
|||||||
158
Moonlight/Shared/Views/Admin/Servers/Manager.razor
Normal file
158
Moonlight/Shared/Views/Admin/Servers/Manager.razor
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
@page "/admin/servers/manager"
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Repositories.Servers
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Daemon.Resources
|
||||||
|
@using Moonlight.App.Models.Wings
|
||||||
|
@using BlazorTable
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
|
@inject NodeRepository NodeRepository
|
||||||
|
@inject NodeService NodeService
|
||||||
|
@inject ServerRepository ServerRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject AlertService AlertService
|
||||||
|
@inject ServerService ServerService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<div class="card mb-5">
|
||||||
|
<div class="card-body">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Refresh"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="() => LazyLoader.Reload()">
|
||||||
|
</WButton>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Stop all"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="StopAll">
|
||||||
|
</WButton>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Kill all"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="KillAll">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="ContainerStats.Container" Items="Containers.Values" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="ContainerStats.Container" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
@{
|
||||||
|
var server = Containers.First(x => x.Value == context).Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
<a href="/server/@(server.Uuid)">@(server.Name)</a>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="ContainerStats.Container" Title="@(SmartTranslateService.Translate("Cpu usage"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
<span>@(context.Cpu)%</span>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="ContainerStats.Container" Title="@(SmartTranslateService.Translate("Memory usage"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
<span>@(Formatter.FormatSize(context.Memory))</span>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="ContainerStats.Container" Title="@(SmartTranslateService.Translate("Network in"))" Field="@(x => x.NetworkIn)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
<span>@(Formatter.FormatSize(context.NetworkIn))</span>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="ContainerStats.Container" Title="@(SmartTranslateService.Translate("Network out"))" Field="@(x => x.NetworkOut)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
<span>@(Formatter.FormatSize(context.NetworkOut))</span>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="ContainerStats.Container" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Name)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
|
@{
|
||||||
|
var server = Containers.First(x => x.Value == context).Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
<a href="/admin/users/view/@(server.Owner.Id)/">@server.Owner.Email</a>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
|
private Dictionary<Server, ContainerStats.Container> Containers = new();
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
Containers.Clear();
|
||||||
|
|
||||||
|
foreach (var node in NodeRepository.Get().ToArray())
|
||||||
|
{
|
||||||
|
await lazyLoader.SetText(node.Name);
|
||||||
|
|
||||||
|
var containerStats = await NodeService.GetContainerStats(node);
|
||||||
|
|
||||||
|
foreach (var container in containerStats.Containers)
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(container.Name, out Guid uuid))
|
||||||
|
{
|
||||||
|
var server = ServerRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Owner)
|
||||||
|
.FirstOrDefault(x => x.Uuid == uuid);
|
||||||
|
|
||||||
|
if (server != null)
|
||||||
|
{
|
||||||
|
Containers.Add(server, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StopAll()
|
||||||
|
{
|
||||||
|
var b = await AlertService.YesNo(
|
||||||
|
SmartTranslateService.Translate("Stop all servers"),
|
||||||
|
SmartTranslateService.Translate("Do you really want to stop all running servers?"),
|
||||||
|
SmartTranslateService.Translate("Yes"),
|
||||||
|
SmartTranslateService.Translate("No")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
foreach (var containerData in Containers)
|
||||||
|
{
|
||||||
|
await ServerService.SetPowerState(containerData.Key, PowerSignal.Stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task KillAll()
|
||||||
|
{
|
||||||
|
var b = await AlertService.YesNo(
|
||||||
|
SmartTranslateService.Translate("Kill all servers"),
|
||||||
|
SmartTranslateService.Translate("Do you really want to kill all running servers?"),
|
||||||
|
SmartTranslateService.Translate("Yes"),
|
||||||
|
SmartTranslateService.Translate("No")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
foreach (var containerData in Containers)
|
||||||
|
{
|
||||||
|
await ServerService.SetPowerState(containerData.Key, PowerSignal.Kill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
Moonlight/Shared/Views/Admin/Users/Edit.razor
Normal file
187
Moonlight/Shared/Views/Admin/Users/Edit.razor
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
@page "/admin/users/edit/{Id:int}"
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|
||||||
|
@inject UserRepository UserRepository
|
||||||
|
@inject SessionService SessionService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
@if (User == null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<TL>No user with this id found</TL>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header border-0 py-0">
|
||||||
|
<h3 class="card-title align-items-start flex-column">
|
||||||
|
<span class="card-label fw-bold fs-3">
|
||||||
|
<TL>Manage user </TL> <span class="text-primary">@(User.Email)</span>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 row">
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body p-10">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>First name</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.FirstName" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Last name</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.LastName" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Email</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.Email" type="email" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 card card-body p-10">
|
||||||
|
<div class="input-group">
|
||||||
|
<select @bind="User.Status" class="form-select">
|
||||||
|
@foreach (var status in (UserStatus[])Enum.GetValues(typeof(UserStatus)))
|
||||||
|
{
|
||||||
|
if (User.Status == status)
|
||||||
|
{
|
||||||
|
<option value="@(status)" selected="">@(status)</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@(status)">@(status)</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Change"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Reloading"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="UpdateStatus">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 card card-body p-10">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<a href="/admin/users" class="btn btn-danger me-3">
|
||||||
|
<TL>Cancel</TL>
|
||||||
|
</a>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Update"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Updating"))"
|
||||||
|
CssClasses="btn-success"
|
||||||
|
OnClick="Update">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-body p-10">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Address</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.Address" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>City</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.City" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>State</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.State" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Country</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.Country" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<input @bind="User.TotpEnabled" type="checkbox" class="form-check-input">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Totp</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<input @bind="User.Admin" type="checkbox" class="form-check-input">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Admin</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 card card-body p-10">
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Discord id</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.DiscordId" type="number" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Discord username</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.DiscordUsername" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-10">
|
||||||
|
<label class="form-label">
|
||||||
|
<TL>Discord discriminator</TL>
|
||||||
|
</label>
|
||||||
|
<input @bind="User.DiscordDiscriminator" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</LazyLoader>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
private User? User;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
User = UserRepository.Get().FirstOrDefault(x => x.Id == Id);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateStatus()
|
||||||
|
{
|
||||||
|
var user = UserRepository.Get().FirstOrDefault(x => x.Id == User!.Id)!;
|
||||||
|
user.Status = User!.Status;
|
||||||
|
UserRepository.Update(user);
|
||||||
|
|
||||||
|
SessionService.ReloadUserSessions(User);
|
||||||
|
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Successfully updated user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Update()
|
||||||
|
{
|
||||||
|
UserRepository.Update(User!);
|
||||||
|
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Successfully updated user"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Created at"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true"/>
|
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Created at"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
<Template>
|
<Template>
|
||||||
<a href="/admin/users/@(context.Id)/edit">
|
<a href="/admin/users/edit/@(context.Id)/">
|
||||||
<TL>Manage</TL>
|
<TL>Manage</TL>
|
||||||
</a>
|
</a>
|
||||||
</Template>
|
</Template>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
@using Moonlight.Shared.Components.Xterm
|
@using Moonlight.Shared.Components.Xterm
|
||||||
@using Moonlight.Shared.Components.ServerControl
|
@using Moonlight.Shared.Components.ServerControl
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
|
||||||
@inject ImageRepository ImageRepository
|
@inject ImageRepository ImageRepository
|
||||||
@inject ServerRepository ServerRepository
|
@inject ServerRepository ServerRepository
|
||||||
@@ -190,18 +191,11 @@
|
|||||||
{
|
{
|
||||||
await lazyLoader.SetText("Requesting tags");
|
await lazyLoader.SetText("Requesting tags");
|
||||||
|
|
||||||
var tags = new List<string>();
|
|
||||||
var image = ImageRepository
|
var image = ImageRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.Tags)
|
|
||||||
.First(x => x.Id == CurrentServer.Image.Id);
|
.First(x => x.Id == CurrentServer.Image.Id);
|
||||||
|
|
||||||
foreach (var tag in image.Tags)
|
Tags = JsonConvert.DeserializeObject<string[]>(image.TagsJson) ?? Array.Empty<string>();
|
||||||
{
|
|
||||||
tags.Add(tag.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Tags = tags.ToArray();
|
|
||||||
Image = image;
|
Image = image;
|
||||||
|
|
||||||
await lazyLoader.SetText("Connecting to console");
|
await lazyLoader.SetText("Connecting to console");
|
||||||
|
|||||||
@@ -330,3 +330,66 @@ Wrong here?;Wrong here?
|
|||||||
A user with this email can not be found;A user with this email can not be found
|
A user with this email can not be found;A user with this email can not be found
|
||||||
Passwort reset successfull. Check your mail;Passwort reset successfull. Check your mail
|
Passwort reset successfull. Check your mail;Passwort reset successfull. Check your mail
|
||||||
Discord bot;Discord bot
|
Discord bot;Discord bot
|
||||||
|
New image;New image
|
||||||
|
Description;Description
|
||||||
|
Uuid;Uuid
|
||||||
|
Enter tag name;Enter tag name
|
||||||
|
Remove;Remove
|
||||||
|
No tags found;No tags found
|
||||||
|
Enter docker image name;Enter docker image name
|
||||||
|
Tags;Tags
|
||||||
|
Docker images;Docker images
|
||||||
|
Default image;Default image
|
||||||
|
Startup command;Startup command
|
||||||
|
Install container;Install container
|
||||||
|
Install entry;Install entry
|
||||||
|
Configuration files;Configuration files
|
||||||
|
Startup detection;Startup detection
|
||||||
|
Stop command;Stop command
|
||||||
|
Successfully saved image;Successfully saved image
|
||||||
|
No docker images found;No docker images found
|
||||||
|
Key;Key
|
||||||
|
Default value;Default value
|
||||||
|
Allocations;Allocations
|
||||||
|
No variables found;No variables found
|
||||||
|
Successfully added image;Successfully added image
|
||||||
|
Password change for;Password change for
|
||||||
|
of;of
|
||||||
|
New node;New node
|
||||||
|
Fqdn;Fqdn
|
||||||
|
Cores used;Cores used
|
||||||
|
used;used
|
||||||
|
5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64
|
||||||
|
Host system information;Host system information
|
||||||
|
0;0
|
||||||
|
Docker containers running;Docker containers running
|
||||||
|
details;details
|
||||||
|
1;1
|
||||||
|
2;2
|
||||||
|
DDos;DDos
|
||||||
|
No ddos attacks found;No ddos attacks found
|
||||||
|
Node;Node
|
||||||
|
Date;Date
|
||||||
|
DDos attack started;DDos attack started
|
||||||
|
packets;packets
|
||||||
|
DDos attack stopped;DDos attack stopped
|
||||||
|
packets; packets
|
||||||
|
Stop all;Stop all
|
||||||
|
Kill all;Kill all
|
||||||
|
Network in;Network in
|
||||||
|
Network out;Network out
|
||||||
|
Kill all servers;Kill all servers
|
||||||
|
Do you really want to kill all running servers?;Do you really want to kill all running servers?
|
||||||
|
Change power state for;Change power state for
|
||||||
|
to;to
|
||||||
|
Stop all servers;Stop all servers
|
||||||
|
Do you really want to stop all running servers?;Do you really want to stop all running servers?
|
||||||
|
Manage ;Manage
|
||||||
|
Manage user ;Manage user
|
||||||
|
Reloading;Reloading
|
||||||
|
Update;Update
|
||||||
|
Updating;Updating
|
||||||
|
Successfully updated user;Successfully updated user
|
||||||
|
Discord id;Discord id
|
||||||
|
Discord username;Discord username
|
||||||
|
Discord discriminator;Discord discriminator
|
||||||
|
|||||||
Reference in New Issue
Block a user