23 Commits
v1b4 ... v1b5

Author SHA1 Message Date
Marcel Baumgartner
9a5b004e17 Merge pull request #163 from Moonlight-Panel/AddUserWebsiteDelete
Added webspace delete
2023-06-12 00:10:27 +02:00
Marcel Baumgartner
3aee059860 Added webspace delete 2023-06-12 00:10:03 +02:00
Marcel Baumgartner
3dfa7f66de Merge pull request #162 from Moonlight-Panel/AddUserDomainDelete
Added user domain delete
2023-06-11 22:10:41 +02:00
Marcel Baumgartner
c2949b4773 Added user domain delete 2023-06-11 22:10:28 +02:00
Marcel Baumgartner
c2d0ab4b1b Merge pull request #161 from Moonlight-Panel/FixServerDelete
Fixed server delete
2023-06-11 21:58:56 +02:00
Marcel Baumgartner
de02f0bd74 Fixed server delete 2023-06-11 21:50:45 +02:00
Marcel Baumgartner
e280a95619 Merge pull request #160 from Moonlight-Panel/AddVersioningSystem
Removed old app version copy instruction
2023-06-11 21:11:39 +02:00
Marcel Baumgartner
6f06be9cc6 Removed old app version copy instruction 2023-06-11 21:11:09 +02:00
Marcel Baumgartner
08745a83b4 Merge pull request #159 from Moonlight-Panel/AddVersioningSystem
Add new version and changelog system
2023-06-11 21:06:24 +02:00
Marcel Baumgartner
9a262d1396 Add new version and changelog system 2023-06-11 20:59:20 +02:00
Marcel Baumgartner
0a1b93b8fb Merge pull request #158 from Moonlight-Panel/ImproveStatistics
Added better statistics calculation and active user messurement
2023-06-11 17:57:01 +02:00
Marcel Baumgartner
4b638fc5da Added better statistics calculation and active user messurement 2023-06-11 17:56:45 +02:00
Marcel Baumgartner
d8e34ae891 Merge pull request #157 from Moonlight-Panel/FixSftpWebsitePort
Fixed website sftp port
2023-06-11 16:32:48 +02:00
Marcel Baumgartner
311237e49d Fixed website sftp port 2023-06-11 16:32:28 +02:00
Marcel Baumgartner
6591bbc927 Merge pull request #156 from Moonlight-Panel/AddServerBackgroundImage
Add dynamic background images for servers
2023-06-11 16:28:03 +02:00
Marcel Baumgartner
43c5717d19 Added default background and optimized change methods 2023-06-11 16:26:43 +02:00
Marcel Baumgartner
61d547b2ce Add dynamic background images for servers 2023-06-10 00:00:54 +02:00
Marcel Baumgartner
d7fbe54225 Merge pull request #151 from Moonlight-Panel/AddUptimeCounter
Added uptime service
2023-06-09 15:02:14 +02:00
Marcel Baumgartner
d0004e9fff Added uptime service 2023-06-09 15:01:51 +02:00
Marcel Baumgartner
829596a3e7 Merge pull request #150 from Moonlight-Panel/AddHealthChecks
Add health checks
2023-06-09 14:39:23 +02:00
Marcel Baumgartner
fc319f0f73 Added better error handling and daemon health check 2023-06-09 14:38:30 +02:00
Marcel Baumgartner
bd8ba11410 Merge pull request #149 from Moonlight-Panel/main
Update AddHealthChecks with latest commits
2023-06-09 14:21:28 +02:00
Marcel Baumgartner
f8fcb86ad8 Added base health check and diagnostic system 2023-06-06 22:50:33 +02:00
40 changed files with 3309 additions and 710 deletions

1
.gitignore vendored
View File

@@ -42,3 +42,4 @@ Desktop.ini
storage/
Moonlight/publish.ps1
Moonlight/version

View File

@@ -20,4 +20,5 @@ public class Image
public List<DockerImage> DockerImages { get; set; } = new();
public List<ImageVariable> Variables { get; set; } = new();
public string TagsJson { get; set; } = "";
public string BackgroundImageUrl { get; set; } = "";
}

View File

@@ -43,6 +43,7 @@ public class User
// Date stuff
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public DateTime LastVisitedAt { get; set; } = DateTime.UtcNow;
// Subscriptions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddBackgroundImageUrlImage : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "BackgroundImageUrl",
table: "Images",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BackgroundImageUrl",
table: "Images");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddLastVisitedTimestamp : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "LastVisitedAt",
table: "Users",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LastVisitedAt",
table: "Users");
}
}
}

View File

@@ -132,6 +132,10 @@ namespace Moonlight.App.Database.Migrations
b.Property<int>("Allocations")
.HasColumnType("int");
b.Property<string>("BackgroundImageUrl")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ConfigFiles")
.IsRequired()
.HasColumnType("longtext");
@@ -758,6 +762,9 @@ namespace Moonlight.App.Database.Migrations
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("LastVisitedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");

View File

@@ -0,0 +1,58 @@
using System.Diagnostics;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Moonlight.App.Database.Entities;
using Moonlight.App.Repositories;
using Moonlight.App.Services;
namespace Moonlight.App.Diagnostics.HealthChecks;
public class DaemonHealthCheck : IHealthCheck
{
private readonly Repository<Node> NodeRepository;
private readonly NodeService NodeService;
public DaemonHealthCheck(Repository<Node> nodeRepository, NodeService nodeService)
{
NodeRepository = nodeRepository;
NodeService = nodeService;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
{
var nodes = NodeRepository.Get().ToArray();
var results = new Dictionary<Node, bool>();
var healthCheckData = new Dictionary<string, object>();
foreach (var node in nodes)
{
try
{
await NodeService.GetCpuMetrics(node);
results.Add(node, true);
}
catch (Exception e)
{
results.Add(node, false);
healthCheckData.Add(node.Name, e.ToStringDemystified());
}
}
var offlineNodes = results
.Where(x => !x.Value)
.ToArray();
if (offlineNodes.Length == nodes.Length)
{
return HealthCheckResult.Unhealthy("All node daemons are offline", null, healthCheckData);
}
if (offlineNodes.Length == 0)
{
return HealthCheckResult.Healthy("All node daemons are online");
}
return HealthCheckResult.Degraded($"{offlineNodes.Length} node daemons are offline", null, healthCheckData);
}
}

View File

@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Moonlight.App.Database;
namespace Moonlight.App.Diagnostics.HealthChecks;
public class DatabaseHealthCheck : IHealthCheck
{
private readonly DataContext DataContext;
public DatabaseHealthCheck(DataContext dataContext)
{
DataContext = dataContext;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = new CancellationToken())
{
try
{
await DataContext.Database.OpenConnectionAsync(cancellationToken);
await DataContext.Database.CloseConnectionAsync();
return HealthCheckResult.Healthy("Database is online");
}
catch (Exception e)
{
return HealthCheckResult.Unhealthy("Database is offline", e);
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Diagnostics;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Moonlight.App.Database.Entities;
using Moonlight.App.Repositories;
using Moonlight.App.Services;
namespace Moonlight.App.Diagnostics.HealthChecks;
public class NodeHealthCheck : IHealthCheck
{
private readonly Repository<Node> NodeRepository;
private readonly NodeService NodeService;
public NodeHealthCheck(Repository<Node> nodeRepository, NodeService nodeService)
{
NodeRepository = nodeRepository;
NodeService = nodeService;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
{
var nodes = NodeRepository.Get().ToArray();
var results = new Dictionary<Node, bool>();
var healthCheckData = new Dictionary<string, object>();
foreach (var node in nodes)
{
try
{
await NodeService.GetStatus(node);
results.Add(node, true);
}
catch (Exception e)
{
results.Add(node, false);
healthCheckData.Add(node.Name, e.ToStringDemystified());
}
}
var offlineNodes = results
.Where(x => !x.Value)
.ToArray();
if (offlineNodes.Length == nodes.Length)
{
return HealthCheckResult.Unhealthy("All nodes are offline", null, healthCheckData);
}
if (offlineNodes.Length == 0)
{
return HealthCheckResult.Healthy("All nodes are online");
}
return HealthCheckResult.Degraded($"{offlineNodes.Length} nodes are offline", null, healthCheckData);
}
}

View File

@@ -0,0 +1,39 @@
using Moonlight.App.Database.Entities;
namespace Moonlight.App.Helpers;
public static class AvgHelper
{
public static StatisticsData[] Calculate(StatisticsData[] data, int splitSize = 40)
{
if (data.Length <= splitSize)
return data;
var result = new List<StatisticsData>();
var i = data.Length / (float)splitSize;
var pc = (int)Math.Round(i);
foreach (var part in data.Chunk(pc))
{
double d = 0;
var res = new StatisticsData();
foreach (var entry in part)
{
d += entry.Value;
}
res.Chart = part.First().Chart;
res.Date = part.First().Date;
if (d == 0)
res.Value = 0;
res.Value = d / part.Length;
result.Add(res);
}
return result.ToArray();
}
}

View File

@@ -17,6 +17,18 @@ public static class Formatter
return $"{t.Hours}h {t.Minutes}m {t.Seconds}s";
}
}
public static string FormatUptime(TimeSpan t)
{
if (t.Days > 0)
{
return $"{t.Days}d {t.Hours}h {t.Minutes}m {t.Seconds}s";
}
else
{
return $"{t.Hours}h {t.Minutes}m {t.Seconds}s";
}
}
private static double Round(this double d, int decimals)
{

View File

@@ -46,6 +46,29 @@ public class ResourcesController : Controller
return NotFound();
}
[HttpGet("background/{name}")]
public async Task<ActionResult> GetBackground([FromRoute] string name)
{
if (name.Contains(".."))
{
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
{
x.Add<string>(name);
});
return NotFound();
}
if (System.IO.File.Exists(PathBuilder.File("storage", "resources", "public", "background", name)))
{
var fs = new FileStream(PathBuilder.File("storage", "resources", "public", "background", name), FileMode.Open);
return File(fs, MimeTypes.GetMimeType(name), name);
}
return NotFound();
}
[HttpGet("bucket/{bucket}/{name}")]
public async Task<ActionResult> GetBucket([FromRoute] string bucket, [FromRoute] string name)

View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Misc;
public class HealthCheck
{
public string Status { get; set; }
public TimeSpan TotalDuration { get; set; }
public Dictionary<string, HealthCheckEntry> Entries { get; set; } = new();
public class HealthCheckEntry
{
public Dictionary<string, string> Data { get; set; } = new();
public string Description { get; set; }
public TimeSpan Duration { get; set; }
public string Status { get; set; }
public List<string> Tags { get; set; } = new();
}
}

View File

@@ -15,6 +15,11 @@ public class ResourceService
{
return $"{AppUrl}/api/moonlight/resources/images/{name}";
}
public string BackgroundImage(string name)
{
return $"{AppUrl}/api/moonlight/resources/background/{name}";
}
public string Avatar(User user)
{

View File

@@ -0,0 +1,101 @@
using Logging.Net;
using Octokit;
using Repository = LibGit2Sharp.Repository;
namespace Moonlight.App.Services;
public class MoonlightService
{
private readonly ConfigService ConfigService;
public readonly DateTime StartTimestamp;
public readonly string AppVersion;
public readonly List<string[]> ChangeLog = new();
public MoonlightService(ConfigService configService)
{
ConfigService = configService;
StartTimestamp = DateTime.UtcNow;
if (File.Exists("version") && !ConfigService.DebugMode)
AppVersion = File.ReadAllText("version");
else if (ConfigService.DebugMode)
{
string repositoryPath = Path.GetFullPath("..");
using var repo = new Repository(repositoryPath);
var commit = repo.Head.Tip;
AppVersion = commit.Sha;
}
else
AppVersion = "unknown";
Task.Run(FetchChangeLog);
}
private async Task FetchChangeLog()
{
if(AppVersion == "unknown")
return;
if (ConfigService.DebugMode)
{
ChangeLog.Add(new[]
{
"Disabled",
"Fetching changelog from github is disabled in debug mode"
});
return;
}
try
{
var client = new GitHubClient(new ProductHeaderValue("Moonlight"));
var pullRequests = await client.PullRequest.GetAllForRepository("Moonlight-Panel", "Moonlight", new PullRequestRequest
{
State = ItemStateFilter.Closed,
SortDirection = SortDirection.Ascending,
SortProperty = PullRequestSort.Created
});
var groupedPullRequests = new Dictionary<DateTime, List<string>>();
foreach (var pullRequest in pullRequests)
{
if (pullRequest.MergedAt != null)
{
var date = pullRequest.MergedAt.Value.Date;
if (!groupedPullRequests.ContainsKey(date))
{
groupedPullRequests[date] = new List<string>();
}
groupedPullRequests[date].Add(pullRequest.Title);
}
}
int i = 1;
foreach (var group in groupedPullRequests)
{
var pullRequestsList = new List<string>();
var date = group.Key.ToString("dd.MM.yyyy");
pullRequestsList.Add($"Patch {i}, {date}");
foreach (var pullRequest in group.Value)
{
pullRequestsList.Add(pullRequest);
}
ChangeLog.Add(pullRequestsList.ToArray());
i++;
}
}
catch (Exception e)
{
Logger.Warn("Error fetching changelog");
Logger.Warn(e);
}
}
}

View File

@@ -19,6 +19,7 @@ namespace Moonlight.App.Services;
public class ServerService
{
private readonly Repository<ServerVariable> ServerVariablesRepository;
private readonly ServerRepository ServerRepository;
private readonly UserRepository UserRepository;
private readonly ImageRepository ImageRepository;
@@ -50,7 +51,8 @@ public class ServerService
NodeService nodeService,
NodeAllocationRepository nodeAllocationRepository,
DateTimeService dateTimeService,
EventSystem eventSystem)
EventSystem eventSystem,
Repository<ServerVariable> serverVariablesRepository)
{
ServerRepository = serverRepository;
WingsApiHelper = wingsApiHelper;
@@ -67,6 +69,7 @@ public class ServerService
NodeAllocationRepository = nodeAllocationRepository;
DateTimeService = dateTimeService;
Event = eventSystem;
ServerVariablesRepository = serverVariablesRepository;
}
private Server EnsureNodeData(Server s)
@@ -401,17 +404,13 @@ public class ServerService
public async Task Delete(Server s)
{
throw new DisplayException("Deleting a server is currently a bit buggy. So its disabled for your safety");
var server = EnsureNodeData(s);
var backups = await GetBackups(server);
var backups = await GetBackups(s);
foreach (var backup in backups)
{
try
{
await DeleteBackup(server, backup);
await DeleteBackup(s, backup);
}
catch (Exception)
{
@@ -419,17 +418,25 @@ public class ServerService
}
}
var server = ServerRepository
.Get()
.Include(x => x.Variables)
.Include(x => x.Node)
.First(x => x.Id == s.Id);
await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null);
//TODO: Fix empty data models
foreach (var variable in server.Variables.ToArray())
{
ServerVariablesRepository.Delete(variable);
}
server.Allocations = new();
server.MainAllocation = null;
server.Variables = new();
server.Backups = new();
ServerRepository.Update(server);
ServerRepository.Delete(server);
}

View File

@@ -0,0 +1,39 @@
using Logging.Net;
using Moonlight.App.Services.Files;
namespace Moonlight.App.Services.Sessions;
public class DynamicBackgroundService
{
public EventHandler OnBackgroundImageChanged { get; set; }
public string BackgroundImageUrl { get; private set; }
private string DefaultBackgroundImageUrl;
public DynamicBackgroundService(ResourceService resourceService)
{
DefaultBackgroundImageUrl = resourceService.BackgroundImage("main.jpg");
BackgroundImageUrl = DefaultBackgroundImageUrl;
}
public Task Change(string url)
{
if(BackgroundImageUrl == url) // Prevent unnecessary updates
return Task.CompletedTask;
BackgroundImageUrl = url;
OnBackgroundImageChanged?.Invoke(this, null!);
return Task.CompletedTask;
}
public Task Reset()
{
if(BackgroundImageUrl == DefaultBackgroundImageUrl) // Prevent unnecessary updates
return Task.CompletedTask;
BackgroundImageUrl = DefaultBackgroundImageUrl;
OnBackgroundImageChanged?.Invoke(this, null!);
return Task.CompletedTask;
}
}

View File

@@ -9,6 +9,7 @@ namespace Moonlight.App.Services.Sessions;
public class SessionService
{
private readonly SessionRepository SessionRepository;
private Repository<User> UserRepository;
private readonly IdentityService IdentityService;
private readonly NavigationManager NavigationManager;
private readonly AlertService AlertService;
@@ -21,13 +22,15 @@ public class SessionService
IdentityService identityService,
NavigationManager navigationManager,
AlertService alertService,
DateTimeService dateTimeService)
DateTimeService dateTimeService,
Repository<User> userRepository)
{
SessionRepository = sessionRepository;
IdentityService = identityService;
NavigationManager = navigationManager;
AlertService = alertService;
DateTimeService = dateTimeService;
UserRepository = userRepository;
}
public async Task Register()
@@ -46,6 +49,12 @@ public class SessionService
};
SessionRepository.Add(OwnSession);
if (user != null) // Track last session init of user as last visited timestamp
{
user.LastVisitedAt = DateTimeService.GetCurrent();
UserRepository.Update(user);
}
}
public void Refresh()

View File

@@ -7,20 +7,33 @@ namespace Moonlight.App.Services.Statistics;
public class StatisticsViewService
{
private readonly StatisticsRepository StatisticsRepository;
private readonly Repository<User> UserRepository;
private readonly DateTimeService DateTimeService;
public StatisticsViewService(StatisticsRepository statisticsRepository, DateTimeService dateTimeService)
public StatisticsViewService(StatisticsRepository statisticsRepository, DateTimeService dateTimeService, Repository<User> userRepository)
{
StatisticsRepository = statisticsRepository;
DateTimeService = dateTimeService;
UserRepository = userRepository;
}
public StatisticsData[] GetData(string chart, StatisticsTimeSpan timeSpan)
{
var startDate = DateTimeService.GetCurrent() - TimeSpan.FromHours((int)timeSpan);
var objs = StatisticsRepository.Get().Where(x => x.Date > startDate && x.Chart == chart);
var objs = StatisticsRepository
.Get()
.Where(x => x.Date > startDate && x.Chart == chart);
return objs.ToArray();
}
public int GetActiveUsers(StatisticsTimeSpan timeSpan)
{
var startDate = DateTimeService.GetCurrent() - TimeSpan.FromHours((int)timeSpan);
return UserRepository
.Get()
.Count(x => x.LastVisitedAt > startDate);
}
}

View File

@@ -72,11 +72,21 @@ public class WebSpaceService
public async Task Delete(WebSpace w)
{
var website = EnsureData(w);
var webSpace = WebSpaceRepository
.Get()
.Include(x => x.Databases)
.Include(x => x.CloudPanel)
.Include(x => x.Owner)
.First(x => x.Id == w.Id);
foreach (var database in webSpace.Databases.ToArray())
{
await DeleteDatabase(webSpace, database);
}
await CloudPanelApiHelper.Delete(website.CloudPanel, $"site/{website.Domain}", null);
await CloudPanelApiHelper.Delete(webSpace.CloudPanel, $"site/{webSpace.Domain}", null);
WebSpaceRepository.Delete(website);
WebSpaceRepository.Delete(webSpace);
}
public async Task<bool> IsHostUp(CloudPanel cloudPanel)

View File

@@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.5" />
<PackageReference Include="Blazor-ApexCharts" Version="0.9.16-beta" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
@@ -23,6 +24,7 @@
<PackageReference Include="FluentFTP" Version="46.0.2" />
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
<PackageReference Include="JWT" Version="10.0.2" />
<PackageReference Include="LibGit2Sharp" Version="0.27.2" />
<PackageReference Include="Logging.Net" Version="1.1.3" />
<PackageReference Include="MailKit" Version="4.0.0" />
<PackageReference Include="Mappy.Net" Version="1.0.2" />
@@ -39,6 +41,7 @@
<PackageReference Include="MineStat" Version="3.1.1" />
<PackageReference Include="MySqlBackup.NET" Version="2.3.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3-beta1" />
<PackageReference Include="Octokit" Version="6.0.0" />
<PackageReference Include="Otp.NET" Version="1.3.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="QRCoder" Version="1.4.3" />
@@ -73,6 +76,7 @@
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
<Folder Include="App\Http\Middleware" />
<Folder Include="storage\backups\" />
<Folder Include="storage\resources\public\background\" />
</ItemGroup>
</Project>

View File

@@ -1,12 +1,14 @@
using BlazorDownloadFile;
using BlazorTable;
using CurrieTechnologies.Razor.SweetAlert2;
using HealthChecks.UI.Client;
using Logging.Net;
using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.ApiClients.Daemon;
using Moonlight.App.ApiClients.Paper;
using Moonlight.App.ApiClients.Wings;
using Moonlight.App.Database;
using Moonlight.App.Diagnostics.HealthChecks;
using Moonlight.App.Events;
using Moonlight.App.Helpers;
using Moonlight.App.Helpers.Wings;
@@ -32,9 +34,6 @@ namespace Moonlight
{
public class Program
{
// App version. Change for release
public static readonly string AppVersion = $"InDev {Formatter.FormatDateOnly(DateTime.Now.Date)}";
public static async Task Main(string[] args)
{
Logger.UsedLogger = new CacheLogger();
@@ -66,6 +65,10 @@ namespace Moonlight
options.HandshakeTimeout = TimeSpan.FromSeconds(10);
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("Database")
.AddCheck<NodeHealthCheck>("Nodes")
.AddCheck<DaemonHealthCheck>("Daemons");
// Databases
builder.Services.AddDbContext<DataContext>();
@@ -128,6 +131,7 @@ namespace Moonlight
builder.Services.AddScoped<ReCaptchaService>();
builder.Services.AddScoped<IpBanService>();
builder.Services.AddSingleton<OAuth2Service>();
builder.Services.AddScoped<DynamicBackgroundService>();
builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>();
@@ -161,6 +165,9 @@ namespace Moonlight
builder.Services.AddSingleton<StatisticsCaptureService>();
builder.Services.AddSingleton<DiscordNotificationService>();
builder.Services.AddSingleton<CleanupService>();
// Other
builder.Services.AddSingleton<MoonlightService>();
// Third party services
builder.Services.AddBlazorTable();
@@ -186,12 +193,18 @@ namespace Moonlight
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.MapHealthChecks("/_health", new()
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
// AutoStart services
_ = app.Services.GetRequiredService<CleanupService>();
_ = app.Services.GetRequiredService<DiscordBotService>();
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
_ = app.Services.GetRequiredService<DiscordNotificationService>();
_ = app.Services.GetRequiredService<MoonlightService>();
// Discord bot service
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>();

View File

@@ -1,18 +1,11 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:26548",
"sslPort": 44339
}
},
"profiles": {
"Moonlight": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ASPNETCORE_ENVIRONMENT": "Development",
"ML_DEBUG": "true"
},
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
"dotnetRunMessages": true
@@ -22,7 +15,8 @@
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ML_SQL_DEBUG": "true"
"ML_SQL_DEBUG": "true",
"ML_DEBUG": "true"
},
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
"dotnetRunMessages": true
@@ -32,17 +26,11 @@
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ML_SQL_DEBUG": "true"
"ML_SQL_DEBUG": "true",
"ML_DEBUG": "true"
},
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
"dotnetRunMessages": true
},
"Docker": {
"commandName": "Docker",
"launchBrowser": false,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true,
"useSSL": true
}
}
}

View File

@@ -0,0 +1,59 @@
@using Moonlight.App.Models.Misc
@using System.Text
@using Moonlight.App.Helpers
@{
string GetStatusColor(string s)
{
if (s == "Healthy")
return "success";
else if (s == "Unhealthy")
return "danger";
else
return "warning";
}
}
<div class="card">
<div class="card-header">
<div class="card-title">
<TL>Moonlight health</TL>:
<div class="ps-3 text-@(GetStatusColor(HealthCheck.Status))">
<TL>@(HealthCheck.Status)</TL>
</div>
</div>
</div>
<div class="card-body">
<div class="accordion" id="healthCheck">
@foreach (var entry in HealthCheck.Entries)
{
<div class="accordion-item">
<h2 class="accordion-header" id="healthCheck_1_header_@(entry.Key.ToLower())">
<button class="accordion-button fs-4 fw-semibold text-@(GetStatusColor(entry.Value.Status))" type="button" data-bs-toggle="collapse" data-bs-target="#healthCheck_body_@(entry.Key.ToLower())">
@(entry.Key)
</button>
</h2>
<div id="healthCheck_body_@(entry.Key.ToLower())" class="accordion-collapse collapse" data-bs-parent="#healthCheck">
<div class="accordion-body">
<b><TL>Status</TL>:</b>&nbsp;<TL>@(entry.Value.Status)</TL><br/>
<b><TL>Description</TL>:</b>&nbsp;@(entry.Value.Description)<br/>
<br/>
@foreach (var x in entry.Value.Data)
{
<b>@(x.Key)</b>
<br/>
@(x.Value)<br/>
}
</div>
</div>
</div>
}
</div>
</div>
</div>
@code
{
[Parameter]
public HealthCheck HealthCheck { get; set; }
}

View File

@@ -10,7 +10,12 @@
<div class="col-xl-4 mb-xl-10">
<div class="card h-md-100">
<div class="card-body d-flex flex-column flex-center">
<img class="img-fluid" src="https://shs.moonlightpanel.xyz/api/screenshot?url=http://@(CurrentWebSpace.Domain)" alt="Website screenshot"/>
<div class="position-relative" style="width: 100%; height: 0; padding-bottom: 56.25%;">
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center">
<img class="img-fluid" src="/assets/media/gif/loading.gif" alt="Placeholder" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;">
<div class="position-absolute top-0 start-0 w-100 h-100" style="background-image: url('https://shs.moonlightpanel.xyz/api/screenshot?url=http://@(CurrentWebSpace.Domain)'); background-size: cover; background-position: center;"></div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,9 +1,16 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject SmartTranslateService SmartTranslateService
@inject WebSpaceService WebSpaceService
@inject NavigationManager NavigationManager
@inject AlertService AlertService
<div class="card card-body">
<div class="row">
<div class="col-8">
<div class="d-flex align-items-center">
<div class="d-flex justify-content-between">
<div class="d-flex">
<div class="symbol symbol-circle me-5">
<div class="symbol-label bg-transparent text-primary border border-secondary border-dashed">
<i class="bx bx-globe bx-md"></i>
@@ -14,13 +21,17 @@
<div class="text-muted fs-5">@(WebSpace.CloudPanel.Name)</div>
</div>
</div>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
OnClick="Delete"
CssClasses="btn-danger">
</WButton>
</div>
</div>
</div>
<div class="row">
<div class="separator my-5"></div>
</div>
<div class="card mb-5 mb-xl-10">
<div class="my-5"></div>
<div class="card mb-xl-10 mb-5">
<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">
@@ -54,4 +65,13 @@
[Parameter]
public WebSpace WebSpace { get; set; }
private async Task Delete()
{
if (await AlertService.ConfirmMath())
{
await WebSpaceService.Delete(WebSpace);
NavigationManager.NavigateTo("/webspaces");
}
}
}

View File

@@ -22,7 +22,7 @@
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="21">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="22">
</div>
</div>
<div class="row fv-row mb-7">

View File

@@ -20,6 +20,7 @@
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
@inject IpBanService IpBanService
@inject DynamicBackgroundService DynamicBackgroundService
<GlobalErrorBoundary>
@{
@@ -56,7 +57,7 @@
<Sidebar></Sidebar>
<div class="app-main flex-column flex-row-fluid" id="kt_app_main">
<div class="d-flex flex-column flex-column-fluid">
<div id="kt_app_content" class="app-content flex-column-fluid">
<div id="kt_app_content" class="app-content flex-column-fluid" style="background-position: center; background-size: cover; background-repeat: no-repeat; background-attachment: fixed; background-image: url('@(DynamicBackgroundService.BackgroundImageUrl)')">
<div id="kt_app_content_container" class="app-container container-fluid">
<div class="mt-10">
<SoftErrorBoundary>
@@ -189,6 +190,11 @@
{
try
{
DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) =>
{
await InvokeAsync(StateHasChanged);
};
IsIpBanned = await IpBanService.IsBanned();
if(IsIpBanned)
@@ -211,7 +217,13 @@
await SessionService.Register();
NavigationManager.LocationChanged += (sender, args) => { SessionService.Refresh(); };
NavigationManager.LocationChanged += async (_, _) =>
{
SessionService.Refresh();
if (!NavigationManager.Uri.Contains("/server/"))
await DynamicBackgroundService.Reset();
};
if (User != null)
{

View File

@@ -3,11 +3,16 @@
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Domains
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Newtonsoft.Json
@using Logging.Net
@inject ServerRepository ServerRepository
@inject UserRepository UserRepository
@inject Repository<WebSpace> WebSpaceRepository
@inject DomainRepository DomainRepository
@inject ConfigService ConfigService
<OnlyAdmin>
<LazyLoader Load="Load">
@@ -97,6 +102,28 @@
</a>
</div>
</div>
<LazyLoader Load="LoadHealthCheckData">
@if (HealthCheckData == null)
{
<div class="card">
<div class="card-header">
<div class="card-title">
<TL>Moonlight health</TL>
</div>
</div>
<div class="card-body">
<div class="alert alert-warning">
<TL>Unable to fetch health check data</TL>
</div>
</div>
</div>
}
else
{
<HealthCheckView HealthCheck="@HealthCheckData"/>
}
</LazyLoader>
</LazyLoader>
</OnlyAdmin>
@@ -107,6 +134,8 @@
private int DomainCount = 0;
private int WebSpaceCount = 0;
private HealthCheck? HealthCheckData;
private Task Load(LazyLoader lazyLoader)
{
ServerCount = ServerRepository.Get().Count();
@@ -116,4 +145,26 @@
return Task.CompletedTask;
}
private async Task LoadHealthCheckData(LazyLoader lazyLoader)
{
await lazyLoader.SetText("Loading health check data");
var appUrl = ConfigService
.GetSection("Moonlight")
.GetValue<string>("AppUrl");
try
{
using var client = new HttpClient();
var json = await client.GetStringAsync($"{appUrl}/_health");
HealthCheckData = JsonConvert.DeserializeObject<HealthCheck>(json) ?? new();
}
catch (Exception e)
{
HealthCheckData = null;
Logger.Warn("Unable to fetch health check data");
Logger.Warn(e);
}
}
}

View File

@@ -13,240 +13,250 @@
@inject FileDownloadService FileDownloadService
<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("Export"))"
WorkingText="@(SmartTranslateService.Translate("Exporting"))"
CssClasses="btn-primary me-3"
OnClick="Export">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-success"
OnClick="Save">
</WButton>
</div>
</div>
</div>
}
</LazyLoader>
<div 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 class="mb-10">
<label class="form-label">
<TL>Background image url</TL>
</label>
<input
@bind="Image.BackgroundImageUrl"
type="text"
class="form-control"
placeholder="@(SmartTranslateService.Translate("Leave empty for the default background image"))">
</div>
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<label class="form-label">
<TL>Tags</TL>
</label>
<div class="input-group mb-5">
<input @bind="AddTagName" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter tag name"))">
<button @onclick="AddTag" class="btn btn-primary">
<TL>Add</TL>
</button>
</div>
<div>
@if (Tags.Any())
{
<div class="row">
@foreach (var tag in Tags)
{
<button @onclick="() => RemoveTag(tag)" class="col m-3 btn btn-outline-primary mw-25">
@(tag)
</button>
}
</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("Export"))"
WorkingText="@(SmartTranslateService.Translate("Exporting"))"
CssClasses="btn-primary me-3"
OnClick="Export">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-success"
OnClick="Save">
</WButton>
</div>
</div>
</div>
}
</LazyLoader>
</div>
</OnlyAdmin>
@code
@@ -330,7 +340,7 @@
{
Image!.DockerImages.Remove(image);
}
private void AddVariable()
{
Image!.Variables.Add(ImageVariable);
@@ -361,7 +371,7 @@
{
Image.TagsJson = JsonConvert.SerializeObject(Tags);
Image.InstallScript = await Editor.GetData();
var json = JsonConvert.SerializeObject(Image, Formatting.Indented);
await FileDownloadService.DownloadString(Image.Name + ".json", json);
}

View File

@@ -66,6 +66,20 @@
}
</div>
}
<div class="row">
<div class="col-sm-6">
<div class="card mt-4">
<div class="card-header">
<div class="card-title">
<TL>Active users</TL>
</div>
</div>
<div class="card-body">
<span class="fs-2">@(ActiveUsers)</span>
</div>
</div>
</div>
</div>
</LazyLoader>
</OnlyAdmin>
@@ -73,7 +87,9 @@
{
private StatisticsTimeSpan StatisticsTimeSpan = StatisticsTimeSpan.Day;
private LazyLoader Loader;
private Dictionary<string, StatisticsData[]> Charts = new();
private int ActiveUsers = 0;
private int TimeSpanBind
{
@@ -91,34 +107,48 @@
Charts.Add(
SmartTranslateService.Translate("Servers"),
StatisticsViewService.GetData("serversCount", StatisticsTimeSpan)
AvgHelper.Calculate(
StatisticsViewService.GetData("serversCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Users"),
StatisticsViewService.GetData("usersCount", StatisticsTimeSpan)
AvgHelper.Calculate(
StatisticsViewService.GetData("usersCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Domains"),
StatisticsViewService.GetData("domainsCount", StatisticsTimeSpan)
AvgHelper.Calculate(
StatisticsViewService.GetData("domainsCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Databases"),
StatisticsViewService.GetData("databasesCount", StatisticsTimeSpan)
AvgHelper.Calculate(
StatisticsViewService.GetData("databasesCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Webspaces"),
StatisticsViewService.GetData("webspacesCount", StatisticsTimeSpan)
AvgHelper.Calculate(
StatisticsViewService.GetData("webspacesCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Sessions"),
StatisticsViewService.GetData("sessionsCount", StatisticsTimeSpan)
AvgHelper.Calculate(
StatisticsViewService.GetData("sessionsCount", StatisticsTimeSpan)
)
);
ActiveUsers = StatisticsViewService.GetActiveUsers(StatisticsTimeSpan);
return Task.CompletedTask;
}

View File

@@ -2,73 +2,102 @@
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@inject HostSystemHelper HostSystemHelper
@inject MoonlightService MoonlightService
<OnlyAdmin>
<AdminSystemNavigation Index="0"/>
<div class="row">
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Version</TL>
</span>
<LazyLoader Load="Load">
<div class="row">
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Version</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>You are running moonlight version</TL>
<span class="text-primary">@(MoonlightService.AppVersion)</span>
</span>
</div>
</div>
</div>
<div class="card-body">
<span class="fs-5">
<TL>You are running moonlight version</TL>
<span class="text-primary">@(Program.AppVersion)</span>
</span>
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Operating system</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>Moonlight is running on</TL>
<span class="text-primary">@(HostSystemHelper.GetOsName())</span>
</span>
</div>
</div>
</div>
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Memory usage</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>Moonlight is using</TL>
<span class="text-primary">@(HostSystemHelper.GetMemoryUsage()) MB</span>
<TL>of memory</TL>
</span>
</div>
</div>
</div>
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Cpu usage</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>Moonlight is using</TL>
<span class="text-primary">@(HostSystemHelper.GetCpuUsage()) %</span>
</span>
</div>
</div>
</div>
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Uptime</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>Moonlight is since</TL>
<span class="text-primary">
@(Formatter.FormatUptime(DateTime.UtcNow - MoonlightService.StartTimestamp))
</span>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Operating system</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>Moonlight is running on</TL>
<span class="text-primary">@(HostSystemHelper.GetOsName())</span>
</span>
</div>
</div>
</div>
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Memory usage</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>Moonlight is using</TL>
<span class="text-primary">@(HostSystemHelper.GetMemoryUsage()) MB</span>
<TL>of memory</TL>
</span>
</div>
</div>
</div>
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Cpu usage</TL>
</span>
</div>
<div class="card-body">
<span class="fs-5">
<TL>Moonlight is using</TL>
<span class="text-primary">@(HostSystemHelper.GetCpuUsage()) %</span>
</span>
</div>
</div>
</div>
</div>
</OnlyAdmin>
</LazyLoader>
</OnlyAdmin>
@code
{
private Task Load(LazyLoader arg)
{
return Task.CompletedTask;
}
}

View File

@@ -1,275 +1,16 @@
@page "/changelog"
@using Moonlight.App.Services
@inject MoonlightService MoonlightService
@{
List<string[]> changelog = new List<string[]>
{
new[]
{
"Patch 1, 13.03.2023",
"Service manager"
},
new[]
{
"Patch 2, 14.03.2023",
"Added new image manager. CRUD implemeted"
},
new[]
{
"Patch 3, 20.03.2023",
"Ddos detection",
"Implemented server manager"
},
new[]
{
"Patch 4, 21.03.2023",
"Added user edit form. Fixed edit link"
},
new[]
{
"Patch 5, 24.03.2023",
"Update Discord Bot Branche",
"Removed discord id and discriminator. Fixed oauth2.",
"Updated discord bot branch",
"Updated smart form branch",
"Added smart form",
"Updating PleskIntegration branch",
"Revert \"Updating PleskIntegration branch\""
},
new[]
{
"Patch 6, 27.03.2023",
"Update Index.razor",
"User settings"
},
new[]
{
"Patch 7, 28.03.2023",
"Added form proccessing screen (not finished). Some ui changes. User m."
},
new[]
{
"Patch 8, 02.04.2023",
"Server manage enhancements",
"Cleanup system",
"Update WingsServerConverter.cs",
"Added quick create dropdown",
"fixed login form",
"Login form fix"
},
new[]
{
"Patch 9, 03.04.2023",
"Fixed cleanup",
"Added server reset setting",
"Totp",
"Update from upstream because of database models",
"Audit log",
"Audit log",
"Force password change",
"Support chat redesign",
"Subscriptions",
"Added server rename setting"
},
new[]
{
"Patch 10, 04.04.2023",
"Added server delete. Tweaked setting names",
"Added server node status screen check thingy",
"Update CacheLogger.cs",
"Update to upstream branch",
"Update View.razor",
"Update to upstream branch",
"Removed legacy aaPanel stuff",
"Update DesignFixes",
"Design fixes",
"Create Changelog.razor"
},
new[]
{
"Patch 11, 05.04.2023",
"Ftp file manager",
"Update to upstream branch",
"Added ActivityStatus"
},
new[]
{
"Patch 12, 06.04.2023",
"Domain overview",
"Plesk integration",
"Add websites to statistics branch",
"Removed legacy database ui",
"Replaced legacy dependency resolve with server service function for w.",
"masu is too stupid to use ulong",
"Statistic system",
"host file access + use in admin resource manager"
},
new[]
{
"Patch 13, 10.04.2023",
"Removed old typeahead. Added own solution. Lang file, notes",
"Implemented website order"
},
new[]
{
"Patch 14, 11.04.2023",
"Implemented domain order. Fixed some bugs"
},
new[]
{
"Patch 15, 12.04.2023",
"Implemented new soft error boundary crash handler",
"Added ip locate. Fixed oauth2 mail send. Fixed mail send when generat.",
"Implemented multi allocation",
"register",
"Fixed server lists",
"News page",
"Improved news system"
},
new[]
{
"Patch 16, 13.04.2023",
"domain view redesigned | fixed some masu at midnight",
"Persistent storage",
"Removed old file manager stuff",
"fixed design of delete button",
"redesigned shared domains screen | split in view/add"
},
new[]
{
"Patch 17, 14.04.2023",
"Removed legacy image tags",
"Fixed server deletion and creation allocation bugs"
},
new[]
{
"Patch 18, 15.04.2023",
"Update DiscordBot branch",
"Fix backup delete",
"Implemented default subscription",
"Implemented random loading message",
"Recode frontend js"
},
new[]
{
"Patch 19, 16.04.2023",
"Optimized allocation search. Added sql command log interception",
"Fixed my stupid mistakes",
"Implemented file view loading animation"
},
new[]
{
"Patch 20, 19.04.2023",
"Cloud panel interation"
},
new[]
{
"Patch 21, 20.04.2023",
"Removed legacy website",
"Added user change status try catch, implement logout. password change",
"Added admin check to sftp login system",
"Implemented new event system",
"Rewritten support chat backend. Added discord notifications"
},
new[]
{
"Patch 22, 21.04.2023",
"Removed old support system",
"Deleted old message system. Replaced it with new event system",
"Update ServerNavigation.razor",
"Switched from internal smtp client to MailKit. Added ssl config"
},
new[]
{
"Patch 23, 23.04.2023",
"Added missing refresh call",
"Added better reconnect screens",
"Removed useless logging. Switched to cache mode for support chat",
"Fixed statistics system"
},
new[]
{
"Patch 24, 24.04.2023",
"Add checking user status to login sequence where login form is currently",
"Added config reload button"
},
new[]
{
"Patch 25, 25.04.2023",
"Update Create.razor",
"Added server count for image overview",
"Implemented owner change option in admin ui"
},
new[]
{
"Patch 26, 26.04.2023",
"Image import export",
"Fixed import and export",
"Prevent message duplication with sender check",
"Implemented forge version switcher"
},
new[]
{
"Patch 27, 28.04.2023",
"Improved user details profile page",
"Implemented fabric version setting"
},
new[]
{
"Patch 28, 29.04.2023",
"update my DiscordBot branch",
"Revert \"update my DiscordBot branch\"",
"Discord bot",
"Update to upstream branch"
},
new[]
{
"Patch 29, 02.05.2023",
"Update Index.razor",
"Added multi line feature and file upload in the support chat"
},
new[]
{
"Patch 30, 04.05.2023",
"Implemented new rating system"
},
new[]
{
"Patch 31, 05.05.2023",
"Code cleanup",
"added version and main jar setting",
"fixed server settings design",
"dotnet runtime settings"
},
new[]
{
"Patch 32, 06.05.2023",
"file create"
},
new[]
{
"Patch 33, 17.05.2023",
"Added node selector for server create screen",
"Added website directory limit for website file manager",
"Added new screenhot service",
"Added node and image view to manager. Made the data handling smoother"
},
new[]
{
"Patch 34, 19.05.2023",
"Added recaptcha. Added recaptcha to register page",
"Implemented ip ban"
}
};
changelog.Reverse();
int i = 0;
}
<div class="card card-docs flex-row-fluid mb-2">
<div class="card-body fs-6 py-15 px-10 py-lg-15 px-lg-15 text-gray-700">
<div class="accordion accordion-flush accordion-icon-toggle" id="kt_accordion">
@foreach (var prt in changelog)
@foreach (var prt in MoonlightService.ChangeLog.ToArray().Reverse())
{
i++;
<div class="accordion-item mb-5">

View File

@@ -1,5 +1,4 @@
@page "/domain/{Id:int}"
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Repositories.Domains
@using Moonlight.App.Database.Entities
@using Microsoft.EntityFrameworkCore
@@ -11,6 +10,8 @@
@inject DomainRepository DomainRepository
@inject DomainService DomainService
@inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager
@inject AlertService AlertService
<LazyLoader Load="Load">
@if (Domain == null)
@@ -28,6 +29,13 @@
<span class="card-title">
<TL>DNS records for</TL><span class="ms-3">@(domainNameBuilt)</span>
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("Delete domain"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="Delete">
</WButton>
</div>
</div>
<div class="mt-5"></div>
<LazyLoader @ref="DnsLazyLoader" Load="LoadDnsRecords">
@@ -240,4 +248,13 @@
await DomainService.DeleteDnsRecord(Domain!, record);
await DnsLazyLoader.Reload();
}
private async Task Delete()
{
if (await AlertService.ConfirmMath())
{
await DomainService.Delete(Domain!);
NavigationManager.NavigateTo("/domains");
}
}
}

View File

@@ -125,111 +125,115 @@
</div>
</div>
<!--d-flex flex-row mb-5-->
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Create something new</TL>
</h3>
</div>
</div>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a gameserver</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>A new gameserver in just a few minutes</TL>
</span>
<div class="row">
<div class="col">
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Create something new</TL>
</h3>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a webspace</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your own websites with a webspace</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a domain</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your servvices accessible throught your own domain</TL>
</span>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a gameserver</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>A new gameserver in just a few minutes</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a webspace</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your own websites with a webspace</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a domain</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your services accessible through your own domain</TL>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Manage your services</TL>
</h3>
</div>
</div>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your gameservers</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Adjust your gameservers</TL>
</span>
<div class="col">
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Manage your services</TL>
</h3>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your webspaces</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Modify the content of your webspaces</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your domains</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Add, edit and delete dns records</TL>
</span>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your gameservers</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Adjust your gameservers</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your webspaces</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Modify the content of your webspaces</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your domains</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Add, edit and delete dns records</TL>
</span>
</div>
</div>
</div>
</div>
</div>

View File

@@ -10,6 +10,7 @@
@using Moonlight.App.Helpers.Wings.Enums
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Xterm
@using Moonlight.Shared.Components.ServerControl
@using Newtonsoft.Json
@@ -20,6 +21,7 @@
@inject EventSystem Event
@inject ServerService ServerService
@inject NavigationManager NavigationManager
@inject DynamicBackgroundService DynamicBackgroundService
@implements IDisposable
@@ -291,6 +293,11 @@
return Task.CompletedTask;
});
if (string.IsNullOrEmpty(Image.BackgroundImageUrl))
await DynamicBackgroundService.Reset();
else
await DynamicBackgroundService.Change(Image.BackgroundImageUrl);
}
}
else

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB