10 Commits
v1b ... v1b1

Author SHA1 Message Date
Marcel Baumgartner
ca05f105cf Merge pull request #130 from Moonlight-Panel/ExternalDiscordBotApi
Added external discord bot api
2023-05-23 21:03:27 +02:00
Marcel Baumgartner
cd4d278ceb Added external discord bot api 2023-05-23 21:03:09 +02:00
Marcel Baumgartner
732fbdd46a Merge pull request #129 from Moonlight-Panel/AddBundleService
Add bundle service
2023-05-23 03:30:38 +02:00
Marcel Baumgartner
a308a067d4 Reworked all css and js imports using new bundler 2023-05-23 03:29:52 +02:00
Marcel Baumgartner
f33b218b17 Added base bundle service 2023-05-23 01:32:49 +02:00
Marcel Baumgartner
6a58275681 Merge pull request #128 from Moonlight-Panel/FixIpBanUpdate
Reload instead of rerender sessions when ip banned
2023-05-22 23:18:39 +02:00
Marcel Baumgartner
eeba837009 Reload instead of rerender sessions when ip banned 2023-05-22 23:18:09 +02:00
Marcel Baumgartner
8098368660 Hopefully fixed errors when building in github actions 2023-05-22 09:46:23 +02:00
Marcel Baumgartner
863ac22036 Update test-docker-build.yml 2023-05-22 09:42:21 +02:00
Marcel Baumgartner
718342d532 Added new github action to find compile errors on commits to main 2023-05-22 09:39:26 +02:00
11 changed files with 433 additions and 75 deletions

18
.github/workflows/test-docker-build.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Test Build
on:
push:
branches: [ "main" ]
pull_request:
types:
- closed
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t moonlightpanel/moonlight:${{ github.sha }} -f Moonlight/Dockerfile .

View File

@@ -8,16 +8,22 @@ using MySql.Data.MySqlClient;
namespace Moonlight.App.Helpers;
public class DatabaseCheckup
public class DatabaseCheckupService
{
public static void Perform()
private readonly ConfigService ConfigService;
public DatabaseCheckupService(ConfigService configService)
{
// This will also copy all default config files
var context = new DataContext(new ConfigService(new StorageService()));
ConfigService = configService;
}
public async Task Perform()
{
var context = new DataContext(ConfigService);
Logger.Info("Checking database");
if (!context.Database.CanConnect())
if (!await context.Database.CanConnectAsync())
{
Logger.Fatal("-----------------------------------------------");
Logger.Fatal("Unable to connect to mysql database");
@@ -32,19 +38,19 @@ public class DatabaseCheckup
Logger.Info("Checking for pending migrations");
var migrations = context.Database
.GetPendingMigrations()
var migrations = (await context.Database
.GetPendingMigrationsAsync())
.ToArray();
if (migrations.Any())
{
Logger.Info($"{migrations.Length} migrations pending. Updating now");
BackupDatabase();
await BackupDatabase();
Logger.Info("Applying migrations");
context.Database.Migrate();
await context.Database.MigrateAsync();
Logger.Info("Successfully applied migrations");
}
@@ -54,7 +60,7 @@ public class DatabaseCheckup
}
}
public static void BackupDatabase()
public async Task BackupDatabase()
{
Logger.Info("Creating backup from database");
@@ -79,14 +85,14 @@ public class DatabaseCheckup
var sw = new Stopwatch();
sw.Start();
using MySqlConnection conn = new MySqlConnection(connectionString);
using MySqlCommand cmd = new MySqlCommand();
await using MySqlConnection conn = new MySqlConnection(connectionString);
await using MySqlCommand cmd = new MySqlCommand();
using MySqlBackup mb = new MySqlBackup(cmd);
cmd.Connection = conn;
conn.Open();
await conn.OpenAsync();
mb.ExportToFile(file);
conn.Close();
await conn.CloseAsync();
sw.Stop();
Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s");

View File

@@ -0,0 +1,149 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.ApiClients.Wings;
using Moonlight.App.ApiClients.Wings.Resources;
using Moonlight.App.Database.Entities;
using Moonlight.App.Http.Requests.DiscordBot.Requests;
using Moonlight.App.Repositories;
using Moonlight.App.Services;
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
[ApiController]
[Route("api/moonlight/discordbot")]
public class DiscordBotController : Controller
{
private readonly Repository<User> UserRepository;
private readonly Repository<Server> ServerRepository;
private readonly ServerService ServerService;
private readonly string Token = "";
private readonly bool Enable;
public DiscordBotController(
Repository<User> userRepository,
Repository<Server> serverRepository,
ServerService serverService,
ConfigService configService)
{
UserRepository = userRepository;
ServerRepository = serverRepository;
ServerService = serverService;
var config = configService
.GetSection("Moonlight")
.GetSection("DiscordBotApi");
Enable = config.GetValue<bool>("Enable");
if (Enable)
{
Token = config.GetValue<string>("Token");
}
}
[HttpGet("{id}/link")]
public async Task<ActionResult> GetLink(ulong id)
{
if (!await IsAuth(Request))
return StatusCode(403);
if (await GetUserFromDiscordId(id) == null)
{
return BadRequest();
}
return Ok();
}
[HttpGet("{id}/servers")]
public async Task<ActionResult<Server[]>> GetServers(ulong id)
{
if (!await IsAuth(Request))
return StatusCode(403);
var user = await GetUserFromDiscordId(id);
if (user == null)
return BadRequest();
return ServerRepository
.Get()
.Include(x => x.Owner)
.Include(x => x.Image)
.Where(x => x.Owner.Id == user.Id)
.ToArray();
}
[HttpPost("{id}/servers/{uuid}")]
public async Task<ActionResult> SetPowerState(ulong id, Guid uuid, [FromBody] SetPowerSignal signal)
{
if (!await IsAuth(Request))
return StatusCode(403);
var user = await GetUserFromDiscordId(id);
if (user == null)
return BadRequest();
var server = ServerRepository
.Get()
.Include(x => x.Owner)
.FirstOrDefault(x => x.Owner.Id == user.Id && x.Uuid == uuid);
if (server == null)
return NotFound();
if (Enum.TryParse(signal.Signal, true, out PowerSignal powerSignal))
{
await ServerService.SetPowerState(server, powerSignal);
return Ok();
}
else
return BadRequest();
}
[HttpGet("{id}/servers/{uuid}")]
public async Task<ActionResult<ServerDetails>> GetServerDetails(ulong id, Guid uuid)
{
if (!await IsAuth(Request))
return StatusCode(403);
var user = await GetUserFromDiscordId(id);
if (user == null)
return BadRequest();
var server = ServerRepository
.Get()
.Include(x => x.Owner)
.FirstOrDefault(x => x.Owner.Id == user.Id && x.Uuid == uuid);
if (server == null)
return NotFound();
return await ServerService.GetDetails(server);
}
private Task<User?> GetUserFromDiscordId(ulong discordId)
{
var user = UserRepository
.Get()
.FirstOrDefault(x => x.DiscordId == discordId);
return Task.FromResult(user);
}
private Task<bool> IsAuth(HttpRequest request)
{
if (!Enable)
return Task.FromResult(false);
if (string.IsNullOrEmpty(request.Headers.Authorization))
return Task.FromResult(false);
if(request.Headers.Authorization == Token)
return Task.FromResult(true);
return Task.FromResult(false);
}
}

View File

@@ -1,10 +1,12 @@
using Logging.Net;
using System.Text;
using Logging.Net;
using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Services;
using Moonlight.App.Services.Files;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
@@ -14,12 +16,14 @@ public class ResourcesController : Controller
{
private readonly SecurityLogService SecurityLogService;
private readonly BucketService BucketService;
private readonly BundleService BundleService;
public ResourcesController(SecurityLogService securityLogService,
BucketService bucketService)
BucketService bucketService, BundleService bundleService)
{
SecurityLogService = securityLogService;
BucketService = bucketService;
BundleService = bundleService;
}
[HttpGet("images/{name}")]
@@ -73,4 +77,34 @@ public class ResourcesController : Controller
return Problem();
}
}
[HttpGet("bundle/js")]
public Task<ActionResult> GetJs()
{
if (BundleService.BundledFinished)
{
return Task.FromResult<ActionResult>(
File(Encoding.ASCII.GetBytes(BundleService.BundledJs), "text/javascript")
);
}
return Task.FromResult<ActionResult>(
NotFound()
);
}
[HttpGet("bundle/css")]
public Task<ActionResult> GetCss()
{
if (BundleService.BundledFinished)
{
return Task.FromResult<ActionResult>(
File(Encoding.ASCII.GetBytes(BundleService.BundledCss), "text/css")
);
}
return Task.FromResult<ActionResult>(
NotFound()
);
}
}

View File

@@ -0,0 +1,8 @@
using Moonlight.App.ApiClients.Wings;
namespace Moonlight.App.Http.Requests.DiscordBot.Requests;
public class SetPowerSignal
{
public string Signal { get; set; }
}

View File

@@ -0,0 +1,129 @@
using Logging.Net;
namespace Moonlight.App.Services.Sessions;
public class BundleService
{
public BundleService(ConfigService configService)
{
var url = configService
.GetSection("Moonlight")
.GetValue<string>("AppUrl");
#region JS
JsFiles = new();
JsFiles.AddRange(new[]
{
url + "/_framework/blazor.server.js",
url + "/assets/plugins/global/plugins.bundle.js",
url + "/_content/XtermBlazor/XtermBlazor.min.js",
url + "/_content/BlazorTable/BlazorTable.min.js",
url + "/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js",
url + "/_content/Blazor.ContextMenu/blazorContextMenu.min.js",
"https://www.google.com/recaptcha/api.js",
"https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js",
"https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js",
"https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js",
url + "/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js",
"require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });",
url + "/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js",
url + "/_content/BlazorMonaco/jsInterop.js",
url + "/assets/js/scripts.bundle.js",
url + "/assets/js/moonlight.js",
"moonlight.loading.registerXterm();",
url + "/_content/Blazor-ApexCharts/js/apex-charts.min.js",
url + "/_content/Blazor-ApexCharts/js/blazor-apex-charts.js"
});
#endregion
#region CSS
CssFiles = new();
CssFiles.AddRange(new[]
{
"https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700",
url + "/assets/css/style.bundle.css",
url + "/assets/css/flashbang.css",
url + "/assets/css/snow.css",
url + "/assets/css/utils.css",
url + "/assets/css/blazor.css",
url + "/_content/XtermBlazor/XtermBlazor.css",
url + "/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css",
url + "/_content/Blazor.ContextMenu/blazorContextMenu.min.css",
url + "/assets/plugins/global/plugins.bundle.css"
});
#endregion
CacheId = Guid.NewGuid().ToString();
Task.Run(Bundle);
}
// Javascript
public string BundledJs { get; private set; }
public readonly List<string> JsFiles;
// CSS
public string BundledCss { get; private set; }
public readonly List<string> CssFiles;
// States
public string CacheId { get; private set; }
public bool BundledFinished { get; set; } = false;
private bool IsBundling { get; set; } = false;
private async Task Bundle()
{
if (!IsBundling)
IsBundling = true;
Logger.Info("Bundling js and css files");
BundledJs = "";
BundledCss = "";
BundledJs = await BundleFiles(
JsFiles
);
BundledCss = await BundleFiles(
CssFiles
);
Logger.Info("Successfully bundled");
BundledFinished = true;
}
private async Task<string> BundleFiles(IEnumerable<string> items)
{
var bundled = "";
using HttpClient client = new HttpClient();
foreach (string item in items)
{
// Item is a url, fetch it
if (item.StartsWith("http"))
{
try
{
var jsCode = await client.GetStringAsync(item);
bundled += jsCode + "\n";
}
catch (Exception e)
{
Logger.Warn($"Error fetching '{item}' while bundling");
Logger.Warn(e);
}
}
else // If not, it is probably a manual addition, so add it
bundled += item + "\n";
}
return bundled;
}
}

View File

@@ -20,6 +20,7 @@ FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
RUN mkdir -p /app/storage
RUN touch /app/storage/donttriggeranyerrors
RUN rm -r /app/storage/*
COPY "Moonlight/defaultstorage" "/app/defaultstorage"
ENTRYPOINT ["dotnet", "Moonlight.dll"]

View File

@@ -2,10 +2,12 @@
@using Moonlight.App.Extensions
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Sessions
@namespace Moonlight.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@inject ConfigService ConfigService
@inject BundleService BundleService
@inject LoadingMessageRepository LoadingMessageRepository
@{
@@ -36,20 +38,29 @@
<link rel="shortcut icon" href="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700"/>
<link rel="stylesheet" type="text/css" href="/assets/css/style.bundle.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/flashbang.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/snow.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/utils.css"/>
@*This import is not in the bundle because the files it references are linked relative to the current lath*@
<link rel="stylesheet" type="text/css" href="/assets/css/boxicons.min.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/blazor.css"/>
<link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
<link rel="stylesheet" type="text/css" href="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css"/>
<link rel="stylesheet" type="text/css" href="/_content/Blazor.ContextMenu/blazorContextMenu.min.css"/>
<link href="/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css"/>
@if (BundleService.BundledFinished)
{
<link rel="stylesheet" type="text/css" href="/api/moonlight/resources/bundle/css?idontwannabecached=@(BundleService.CacheId)"/>
}
else
{
foreach (var cssFile in BundleService.CssFiles)
{
if (cssFile.StartsWith("http"))
{
<link rel="stylesheet" type="text/css" href="@(cssFile)">
}
else
{
<style>
@cssFile
</style>
}
}
}
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<base href="~/"/>
@@ -95,33 +106,24 @@
</div>
</div>
<script src="/_framework/blazor.server.js"></script>
<script src="/assets/plugins/global/plugins.bundle.js"></script>
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
<script src="/_content/BlazorInputFile/inputfile.js"></script>
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
<script src="https://www.google.com/recaptcha/api.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
<script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="/_content/BlazorMonaco/jsInterop.js"></script>
<script src="/assets/js/scripts.bundle.js"></script>
<script src="/assets/js/moonlight.js"></script>
<script>
moonlight.loading.registerXterm();
</script>
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
@if (BundleService.BundledFinished)
{
<script src="/api/moonlight/resources/bundle/js?idontwannabecached=@(BundleService.CacheId)">
</script>
}
else
{
foreach (var jsFile in BundleService.JsFiles)
{
if (jsFile.StartsWith("http"))
{
<script src="@(jsFile)"></script>
}
else
{
@Html.Raw("<script>" + jsFile +"</script>")
}
}
}
</body>
</html>

View File

@@ -34,13 +34,19 @@ namespace Moonlight
// App version. Change for release
public static readonly string AppVersion = $"InDev {Formatter.FormatDateOnly(DateTime.Now.Date)}";
public static void Main(string[] args)
public static async Task Main(string[] args)
{
Logger.UsedLogger = new CacheLogger();
Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}");
DatabaseCheckup.Perform();
Logger.Info("Running pre-init tasks");
// This will also copy all default config files
var configService = new ConfigService(new StorageService());
var databaseCheckupService = new DatabaseCheckupService(configService);
await databaseCheckupService.Perform();
var builder = WebApplication.CreateBuilder(args);
@@ -121,6 +127,7 @@ namespace Moonlight
builder.Services.AddScoped<ReCaptchaService>();
builder.Services.AddScoped<IpBanService>();
builder.Services.AddSingleton<OAuth2Service>();
builder.Services.AddSingleton<BundleService>();
builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>();
@@ -189,7 +196,7 @@ namespace Moonlight
// Discord bot service
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
app.Run();
await app.RunAsync();
}
}
}

View File

@@ -194,10 +194,10 @@
if(IsIpBanned)
await InvokeAsync(StateHasChanged);
await Event.On<Object>("ipBan.update", this, async o =>
await Event.On<Object>("ipBan.update", this, async _ =>
{
IsIpBanned = await IpBanService.IsBanned();
await InvokeAsync(StateHasChanged);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
});
User = await IdentityService.Get();

View File

@@ -8,6 +8,10 @@
"Port": "10324",
"Username": "user_name"
},
"DiscordBotApi": {
"Enable": false,
"Token": "you api key here"
},
"DiscordBot": {
"Enable": false,
"Token": "Discord.Token.Here",