Merge pull request #236 from Moonlight-Panel/AddPluginSystem
Add plugin system
This commit is contained in:
commit
f29206a69b
12
Moonlight/App/Helpers/ComponentHelper.cs
Normal file
12
Moonlight/App/Helpers/ComponentHelper.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class ComponentHelper
|
||||||
|
{
|
||||||
|
public static RenderFragment FromType(Type type) => builder =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent(0, type);
|
||||||
|
builder.CloseComponent();
|
||||||
|
};
|
||||||
|
}
|
6
Moonlight/App/Models/Misc/OfficialMoonlightPlugin.cs
Normal file
6
Moonlight/App/Models/Misc/OfficialMoonlightPlugin.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public class OfficialMoonlightPlugin
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
|
@ -16,6 +16,13 @@ public static class Permissions
|
||||||
Description = "View statistical information about the moonlight instance"
|
Description = "View statistical information about the moonlight instance"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static Permission AdminSysPlugins = new()
|
||||||
|
{
|
||||||
|
Index = 2,
|
||||||
|
Name = "Admin system plugins",
|
||||||
|
Description = "View and install plugins"
|
||||||
|
};
|
||||||
|
|
||||||
public static Permission AdminDomains = new()
|
public static Permission AdminDomains = new()
|
||||||
{
|
{
|
||||||
Index = 4,
|
Index = 4,
|
||||||
|
|
15
Moonlight/App/Plugin/MoonlightPlugin.cs
Normal file
15
Moonlight/App/Plugin/MoonlightPlugin.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using Moonlight.App.Plugin.UI.Servers;
|
||||||
|
using Moonlight.App.Plugin.UI.Webspaces;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Plugin;
|
||||||
|
|
||||||
|
public abstract class MoonlightPlugin
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "N/A";
|
||||||
|
public string Author { get; set; } = "N/A";
|
||||||
|
public string Version { get; set; } = "N/A";
|
||||||
|
|
||||||
|
public Func<ServerPageContext, Task>? OnBuildServerPage { get; set; }
|
||||||
|
public Func<WebspacePageContext, Task>? OnBuildWebspacePage { get; set; }
|
||||||
|
public Func<IServiceCollection, Task>? OnBuildServices { get; set; }
|
||||||
|
}
|
12
Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs
Normal file
12
Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Plugin.UI.Servers;
|
||||||
|
|
||||||
|
public class ServerPageContext
|
||||||
|
{
|
||||||
|
public List<ServerTab> Tabs { get; set; } = new();
|
||||||
|
public List<ServerSetting> Settings { get; set; } = new();
|
||||||
|
public Server Server { get; set; }
|
||||||
|
public User User { get; set; }
|
||||||
|
public string[] ImageTags { get; set; }
|
||||||
|
}
|
9
Moonlight/App/Plugin/UI/Servers/ServerSetting.cs
Normal file
9
Moonlight/App/Plugin/UI/Servers/ServerSetting.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Plugin.UI.Servers;
|
||||||
|
|
||||||
|
public class ServerSetting
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public RenderFragment Component { get; set; }
|
||||||
|
}
|
11
Moonlight/App/Plugin/UI/Servers/ServerTab.cs
Normal file
11
Moonlight/App/Plugin/UI/Servers/ServerTab.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Plugin.UI.Servers;
|
||||||
|
|
||||||
|
public class ServerTab
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Route { get; set; }
|
||||||
|
public string Icon { get; set; }
|
||||||
|
public RenderFragment Component { get; set; }
|
||||||
|
}
|
10
Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs
Normal file
10
Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Plugin.UI.Webspaces;
|
||||||
|
|
||||||
|
public class WebspacePageContext
|
||||||
|
{
|
||||||
|
public List<WebspaceTab> Tabs { get; set; } = new();
|
||||||
|
public User User { get; set; }
|
||||||
|
public WebSpace WebSpace { get; set; }
|
||||||
|
}
|
10
Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs
Normal file
10
Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Plugin.UI.Webspaces;
|
||||||
|
|
||||||
|
public class WebspaceTab
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "N/A";
|
||||||
|
public string Route { get; set; } = "/";
|
||||||
|
public RenderFragment Component { get; set; }
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ public class StorageService
|
||||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "resources"));
|
Directory.CreateDirectory(PathBuilder.Dir("storage", "resources"));
|
||||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
|
Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
|
||||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
|
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
|
||||||
|
Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
|
||||||
|
|
||||||
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
|
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class MoonlightService
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = new GitHubClient(new ProductHeaderValue("Moonlight"));
|
var client = new GitHubClient(new ProductHeaderValue("Moonlight-Panel"));
|
||||||
|
|
||||||
var pullRequests = await client.PullRequest.GetAllForRepository("Moonlight-Panel", "Moonlight", new PullRequestRequest
|
var pullRequests = await client.PullRequest.GetAllForRepository("Moonlight-Panel", "Moonlight", new PullRequestRequest
|
||||||
{
|
{
|
||||||
|
|
111
Moonlight/App/Services/Plugins/PluginService.cs
Normal file
111
Moonlight/App/Services/Plugins/PluginService.cs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Plugin;
|
||||||
|
using Moonlight.App.Plugin.UI.Servers;
|
||||||
|
using Moonlight.App.Plugin.UI.Webspaces;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Plugins;
|
||||||
|
|
||||||
|
public class PluginService
|
||||||
|
{
|
||||||
|
public List<MoonlightPlugin> Plugins { get; private set; }
|
||||||
|
public Dictionary<MoonlightPlugin, string> PluginFiles { get; private set; }
|
||||||
|
|
||||||
|
private AssemblyLoadContext LoadContext;
|
||||||
|
|
||||||
|
public PluginService()
|
||||||
|
{
|
||||||
|
LoadContext = new(null, true);
|
||||||
|
ReloadPlugins().Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task UnloadPlugins()
|
||||||
|
{
|
||||||
|
Plugins = new();
|
||||||
|
PluginFiles = new();
|
||||||
|
|
||||||
|
if(LoadContext.Assemblies.Any())
|
||||||
|
LoadContext.Unload();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReloadPlugins()
|
||||||
|
{
|
||||||
|
await UnloadPlugins();
|
||||||
|
|
||||||
|
// Try to update all plugins ending with .dll.cache
|
||||||
|
foreach (var pluginFile in Directory.EnumerateFiles(
|
||||||
|
PathBuilder.Dir(Directory.GetCurrentDirectory(), "storage", "plugins"))
|
||||||
|
.Where(x => x.EndsWith(".dll.cache")))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var realPath = pluginFile.Replace(".cache", "");
|
||||||
|
File.Copy(pluginFile, realPath, true);
|
||||||
|
File.Delete(pluginFile);
|
||||||
|
Logger.Info($"Updated plugin {realPath} on startup");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginType = typeof(MoonlightPlugin);
|
||||||
|
|
||||||
|
foreach (var pluginFile in Directory.EnumerateFiles(
|
||||||
|
PathBuilder.Dir(Directory.GetCurrentDirectory(), "storage", "plugins"))
|
||||||
|
.Where(x => x.EndsWith(".dll")))
|
||||||
|
{
|
||||||
|
var assembly = LoadContext.LoadFromAssemblyPath(pluginFile);
|
||||||
|
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (type.IsSubclassOf(pluginType))
|
||||||
|
{
|
||||||
|
var plugin = (Activator.CreateInstance(type) as MoonlightPlugin)!;
|
||||||
|
|
||||||
|
Logger.Info($"Loaded plugin '{plugin.Name}' ({plugin.Version}) by {plugin.Author}");
|
||||||
|
|
||||||
|
Plugins.Add(plugin);
|
||||||
|
PluginFiles.Add(plugin, pluginFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info($"Loaded {Plugins.Count} plugins");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServerPageContext> BuildServerPage(ServerPageContext context)
|
||||||
|
{
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
if (plugin.OnBuildServerPage != null)
|
||||||
|
await plugin.OnBuildServerPage.Invoke(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WebspacePageContext> BuildWebspacePage(WebspacePageContext context)
|
||||||
|
{
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
if (plugin.OnBuildWebspacePage != null)
|
||||||
|
await plugin.OnBuildWebspacePage.Invoke(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task BuildServices(IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
if (plugin.OnBuildServices != null)
|
||||||
|
await plugin.OnBuildServices.Invoke(serviceCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
Moonlight/App/Services/Plugins/PluginStoreService.cs
Normal file
63
Moonlight/App/Services/Plugins/PluginStoreService.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System.Text;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Octokit;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Plugins;
|
||||||
|
|
||||||
|
public class PluginStoreService
|
||||||
|
{
|
||||||
|
private readonly GitHubClient Client;
|
||||||
|
private readonly PluginService PluginService;
|
||||||
|
|
||||||
|
public PluginStoreService(PluginService pluginService)
|
||||||
|
{
|
||||||
|
PluginService = pluginService;
|
||||||
|
Client = new(new ProductHeaderValue("Moonlight-Panel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OfficialMoonlightPlugin[]> GetPlugins()
|
||||||
|
{
|
||||||
|
var items = await Client.Repository.Content.GetAllContents("Moonlight-Panel", "OfficialPlugins");
|
||||||
|
|
||||||
|
if (items == null)
|
||||||
|
{
|
||||||
|
Logger.Fatal("Unable to read plugin repo contents");
|
||||||
|
return Array.Empty<OfficialMoonlightPlugin>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
.Where(x => x.Type == ContentType.Dir)
|
||||||
|
.Select(x => new OfficialMoonlightPlugin()
|
||||||
|
{
|
||||||
|
Name = x.Name
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetPluginReadme(OfficialMoonlightPlugin plugin)
|
||||||
|
{
|
||||||
|
var rawReadme = await Client.Repository.Content
|
||||||
|
.GetRawContent("Moonlight-Panel", "OfficialPlugins", $"{plugin.Name}/README.md");
|
||||||
|
|
||||||
|
if (rawReadme == null)
|
||||||
|
return "Error";
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(rawReadme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InstallPlugin(OfficialMoonlightPlugin plugin, bool updating = false)
|
||||||
|
{
|
||||||
|
var rawPlugin = await Client.Repository.Content
|
||||||
|
.GetRawContent("Moonlight-Panel", "OfficialPlugins", $"{plugin.Name}/{plugin.Name}.dll");
|
||||||
|
|
||||||
|
if (updating)
|
||||||
|
{
|
||||||
|
await File.WriteAllBytesAsync(PathBuilder.File("storage", "plugins", $"{plugin.Name}.dll.cache"), rawPlugin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await File.WriteAllBytesAsync(PathBuilder.File("storage", "plugins", $"{plugin.Name}.dll"), rawPlugin);
|
||||||
|
await PluginService.ReloadPlugins();
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,6 +90,7 @@
|
||||||
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
|
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
|
||||||
<Folder Include="App\Http\Middleware" />
|
<Folder Include="App\Http\Middleware" />
|
||||||
<Folder Include="storage\backups\" />
|
<Folder Include="storage\backups\" />
|
||||||
|
<Folder Include="storage\plugins\" />
|
||||||
<Folder Include="storage\resources\public\background\" />
|
<Folder Include="storage\resources\public\background\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ using Moonlight.App.Services.Interop;
|
||||||
using Moonlight.App.Services.Mail;
|
using Moonlight.App.Services.Mail;
|
||||||
using Moonlight.App.Services.Minecraft;
|
using Moonlight.App.Services.Minecraft;
|
||||||
using Moonlight.App.Services.Notifications;
|
using Moonlight.App.Services.Notifications;
|
||||||
|
using Moonlight.App.Services.Plugins;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using Moonlight.App.Services.Statistics;
|
using Moonlight.App.Services.Statistics;
|
||||||
using Moonlight.App.Services.SupportChat;
|
using Moonlight.App.Services.SupportChat;
|
||||||
|
@ -110,6 +111,9 @@ namespace Moonlight
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
var pluginService = new PluginService();
|
||||||
|
await pluginService.BuildServices(builder.Services);
|
||||||
|
|
||||||
// Switch to logging.net injection
|
// Switch to logging.net injection
|
||||||
// TODO: Enable in production
|
// TODO: Enable in production
|
||||||
builder.Logging.ClearProviders();
|
builder.Logging.ClearProviders();
|
||||||
|
@ -208,6 +212,7 @@ namespace Moonlight
|
||||||
builder.Services.AddScoped<PopupService>();
|
builder.Services.AddScoped<PopupService>();
|
||||||
builder.Services.AddScoped<SubscriptionService>();
|
builder.Services.AddScoped<SubscriptionService>();
|
||||||
builder.Services.AddScoped<BillingService>();
|
builder.Services.AddScoped<BillingService>();
|
||||||
|
builder.Services.AddSingleton<PluginStoreService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<SessionClientService>();
|
builder.Services.AddScoped<SessionClientService>();
|
||||||
builder.Services.AddSingleton<SessionServerService>();
|
builder.Services.AddSingleton<SessionServerService>();
|
||||||
|
@ -240,6 +245,8 @@ namespace Moonlight
|
||||||
builder.Services.AddSingleton<TelemetryService>();
|
builder.Services.AddSingleton<TelemetryService>();
|
||||||
builder.Services.AddSingleton<TempMailService>();
|
builder.Services.AddSingleton<TempMailService>();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(pluginService);
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
builder.Services.AddSingleton<MoonlightService>();
|
builder.Services.AddSingleton<MoonlightService>();
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,11 @@
|
||||||
<TL>Mail</TL>
|
<TL>Mail</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 10 ? "active" : "")" href="/admin/system/plugins">
|
||||||
|
<TL>Plugins</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Plugin.UI.Webspaces
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@ -34,26 +35,14 @@
|
||||||
<div class="card mb-xl-10 mb-5">
|
<div class="card mb-xl-10 mb-5">
|
||||||
<div class="card-body pt-0 pb-0">
|
<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">
|
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||||
|
@foreach (var tab in Context.Tabs)
|
||||||
|
{
|
||||||
<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="/webspace/@(WebSpace.Id)">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Route == tab.Route ? "active" : "")" href="/webspace/@(WebSpace.Id + tab.Route)">
|
||||||
<TL>Dashboard</TL>
|
<TL>@(tab.Name)</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="/webspace/@(WebSpace.Id)/files">
|
|
||||||
<TL>Files</TL>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item mt-2">
|
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/webspace/@(WebSpace.Id)/sftp">
|
|
||||||
<TL>Sftp</TL>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item mt-2">
|
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/webspace/@(WebSpace.Id)/databases">
|
|
||||||
<TL>Databases</TL>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,11 +50,14 @@
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int Index { get; set; }
|
public string Route { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public WebSpace WebSpace { get; set; }
|
public WebSpace WebSpace { get; set; }
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
public WebspacePageContext Context { get; set; }
|
||||||
|
|
||||||
private async Task Delete()
|
private async Task Delete()
|
||||||
{
|
{
|
||||||
if (await AlertService.ConfirmMath())
|
if (await AlertService.ConfirmMath())
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
{
|
{
|
||||||
SecurityLogs = SecurityLogRepository
|
SecurityLogs = SecurityLogRepository
|
||||||
.Get()
|
.Get()
|
||||||
|
.ToArray()
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
138
Moonlight/Shared/Views/Admin/Sys/Plugins.razor
Normal file
138
Moonlight/Shared/Views/Admin/Sys/Plugins.razor
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
@page "/admin/system/plugins"
|
||||||
|
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using Moonlight.App.Services.Plugins
|
||||||
|
@using BlazorTable
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Plugin
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject PluginStoreService PluginStoreService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject PluginService PluginService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
@inject ModalService ModalService
|
||||||
|
|
||||||
|
@attribute [PermissionRequired(nameof(Permissions.AdminSysPlugins))]
|
||||||
|
|
||||||
|
<AdminSystemNavigation Index="10"/>
|
||||||
|
|
||||||
|
<div class="card mb-5">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<TL>Installed plugins</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="MoonlightPlugin" Items="PluginService.Plugins" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="MoonlightPlugin" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Filterable="true" Sortable="false"/>
|
||||||
|
<Column TableItem="MoonlightPlugin" Title="@(SmartTranslateService.Translate("Author"))" Field="@(x => x.Author)" Filterable="true" Sortable="false"/>
|
||||||
|
<Column TableItem="MoonlightPlugin" Title="@(SmartTranslateService.Translate("Version"))" Field="@(x => x.Version)" Filterable="true" Sortable="false"/>
|
||||||
|
<Column TableItem="MoonlightPlugin" Title="@(SmartTranslateService.Translate("Path"))" Field="@(x => x.Name)" Filterable="false" Sortable="false">
|
||||||
|
<Template>
|
||||||
|
@{
|
||||||
|
var path = PluginService.PluginFiles[context];
|
||||||
|
}
|
||||||
|
|
||||||
|
<span>@(path)</span>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<TL>Official plugins</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<LazyLoader @ref="PluginsLazyLoader" Load="LoadOfficialPlugins">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="OfficialMoonlightPlugin" Items="PluginList" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="OfficialMoonlightPlugin" Width="80%" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Filterable="true" Sortable="false"/>
|
||||||
|
<Column TableItem="OfficialMoonlightPlugin" Width="10%" Title="" Field="@(x => x.Name)" Filterable="false" Sortable="false">
|
||||||
|
<Template>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Show readme"))"
|
||||||
|
CssClasses="btn-secondary"
|
||||||
|
OnClick="() => ShowOfficialPluginReadme(context)"/>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="OfficialMoonlightPlugin" Width="10%" Title="" Field="@(x => x.Name)" Filterable="false" Sortable="false">
|
||||||
|
<Template>
|
||||||
|
@if (PluginService.PluginFiles.Values.Any(x =>
|
||||||
|
Path.GetFileName(x).Replace(".dll", "") == context.Name))
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Update"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Updating"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="() => UpdateOfficialPlugin(context)"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Install"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Installing"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="() => InstallOfficialPlugin(context)"/>
|
||||||
|
}
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="pluginReadme" class="modal" style="display: none">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<TL>Plugin readme</TL>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
@((MarkupString)Markdig.Markdown.ToHtml(PluginReadme))
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private LazyLoader PluginsLazyLoader;
|
||||||
|
private OfficialMoonlightPlugin[] PluginList;
|
||||||
|
private string PluginReadme = "";
|
||||||
|
|
||||||
|
private async Task LoadOfficialPlugins(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
PluginList = await PluginStoreService.GetPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowOfficialPluginReadme(OfficialMoonlightPlugin plugin)
|
||||||
|
{
|
||||||
|
PluginReadme = await PluginStoreService.GetPluginReadme(plugin);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await ModalService.Show("pluginReadme");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InstallOfficialPlugin(OfficialMoonlightPlugin plugin)
|
||||||
|
{
|
||||||
|
await PluginStoreService.InstallPlugin(plugin);
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Successfully installed plugin"));
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateOfficialPlugin(OfficialMoonlightPlugin plugin)
|
||||||
|
{
|
||||||
|
await PluginStoreService.InstallPlugin(plugin, true);
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Successfully installed plugin. You need to reboot to apply changes"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,17 @@
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Events
|
@using Moonlight.App.Events
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Helpers.Wings
|
@using Moonlight.App.Helpers.Wings
|
||||||
@using Moonlight.App.Helpers.Wings.Enums
|
@using Moonlight.App.Helpers.Wings.Enums
|
||||||
|
@using Moonlight.App.Plugin.UI
|
||||||
|
@using Moonlight.App.Plugin.UI.Servers
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Plugins
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
@using Moonlight.Shared.Components.Xterm
|
@using Moonlight.Shared.Components.Xterm
|
||||||
|
@using Moonlight.Shared.Views.Server.Settings
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
|
|
||||||
@inject ImageRepository ImageRepository
|
@inject ImageRepository ImageRepository
|
||||||
|
@ -21,10 +26,11 @@
|
||||||
@inject DynamicBackgroundService DynamicBackgroundService
|
@inject DynamicBackgroundService DynamicBackgroundService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
|
@inject PluginService PluginService
|
||||||
|
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
<LazyLoader Load="LoadData">
|
<LazyLoader Load="Load">
|
||||||
@if (CurrentServer == null)
|
@if (CurrentServer == null)
|
||||||
{
|
{
|
||||||
<NotFoundAlert/>
|
<NotFoundAlert/>
|
||||||
|
@ -33,7 +39,7 @@
|
||||||
{
|
{
|
||||||
if (NodeOnline)
|
if (NodeOnline)
|
||||||
{
|
{
|
||||||
if (Console.ConsoleState == ConsoleState.Connected)
|
if (Console != null && Console.ConsoleState == ConsoleState.Connected)
|
||||||
{
|
{
|
||||||
if (Console.ServerState == ServerState.Installing || CurrentServer.Installing)
|
if (Console.ServerState == ServerState.Installing || CurrentServer.Installing)
|
||||||
{
|
{
|
||||||
|
@ -75,47 +81,36 @@
|
||||||
<CascadingValue Value="Console">
|
<CascadingValue Value="Console">
|
||||||
<CascadingValue Value="CurrentServer">
|
<CascadingValue Value="CurrentServer">
|
||||||
<CascadingValue Value="Tags">
|
<CascadingValue Value="Tags">
|
||||||
|
<CascadingValue Value="Context">
|
||||||
<SmartRouter Route="@Route">
|
<SmartRouter Route="@Route">
|
||||||
<Route Path="/">
|
@foreach (var tab in Context.Tabs)
|
||||||
<ServerNavigation Index="0">
|
{
|
||||||
<ServerConsole/>
|
<Route Path="@(tab.Route)">
|
||||||
</ServerNavigation>
|
<ServerNavigation Route="@(tab.Route)">
|
||||||
</Route>
|
@(tab.Component)
|
||||||
<Route Path="/files">
|
|
||||||
<ServerNavigation Index="1">
|
|
||||||
<ServerFiles/>
|
|
||||||
</ServerNavigation>
|
|
||||||
</Route>
|
|
||||||
<Route Path="/backups">
|
|
||||||
<ServerNavigation Index="2">
|
|
||||||
<ServerBackups/>
|
|
||||||
</ServerNavigation>
|
|
||||||
</Route>
|
|
||||||
<Route Path="/network">
|
|
||||||
<ServerNavigation Index="3">
|
|
||||||
<ServerNetwork/>
|
|
||||||
</ServerNavigation>
|
|
||||||
</Route>
|
|
||||||
<Route Path="/addons">
|
|
||||||
<ServerNavigation Index="4">
|
|
||||||
<ServerAddons/>
|
|
||||||
</ServerNavigation>
|
|
||||||
</Route>
|
|
||||||
<Route Path="/settings">
|
|
||||||
<ServerNavigation Index="5">
|
|
||||||
<ServerSettings/>
|
|
||||||
</ServerNavigation>
|
</ServerNavigation>
|
||||||
</Route>
|
</Route>
|
||||||
|
}
|
||||||
</SmartRouter>
|
</SmartRouter>
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
|
</CascadingValue>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="alert alert-info">
|
<div class="d-flex justify-content-center flex-center">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h1 class="card-title">
|
||||||
<TL>Connecting</TL>
|
<TL>Connecting</TL>
|
||||||
|
</h1>
|
||||||
|
<p class="card-text fs-4">
|
||||||
|
<TL>Connecting to the servers console to stream logs and the current resource usage</TL>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,21 +139,19 @@
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string ServerUuid { get; set; }
|
public string ServerUuid { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string? Route { get; set; }
|
public string? Route { get; set; }
|
||||||
|
|
||||||
private WingsConsole? Console;
|
private WingsConsole? Console;
|
||||||
private Server? CurrentServer;
|
private Server? CurrentServer;
|
||||||
private Node Node;
|
|
||||||
private bool NodeOnline = false;
|
private bool NodeOnline = false;
|
||||||
private Image Image;
|
|
||||||
private NodeAllocation NodeAllocation;
|
private NodeAllocation NodeAllocation;
|
||||||
private string[] Tags;
|
private string[] Tags;
|
||||||
|
|
||||||
private Terminal? InstallConsole;
|
private Terminal? InstallConsole;
|
||||||
|
|
||||||
|
private ServerPageContext Context;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Console = new();
|
Console = new();
|
||||||
|
@ -171,68 +164,61 @@
|
||||||
|
|
||||||
Console.OnMessage += async (_, s) =>
|
Console.OnMessage += async (_, s) =>
|
||||||
{
|
{
|
||||||
if (Console.ServerState == ServerState.Installing)
|
if (Console.ServerState == ServerState.Installing && InstallConsole != null)
|
||||||
{
|
|
||||||
if (InstallConsole != null)
|
|
||||||
{
|
{
|
||||||
if (s.IsInternal)
|
if (s.IsInternal)
|
||||||
await InstallConsole.WriteLine("\x1b[38;5;16;48;5;135m\x1b[39m\x1b[1m Moonlight \x1b[0m " + s.Content + "\x1b[0m");
|
await InstallConsole.WriteLine("\x1b[38;5;16;48;5;135m\x1b[39m\x1b[1m Moonlight \x1b[0m " + s.Content + "\x1b[0m");
|
||||||
else
|
else
|
||||||
await InstallConsole.WriteLine(s.Content);
|
await InstallConsole.WriteLine(s.Content);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadData(LazyLoader lazyLoader)
|
private async Task Load(LazyLoader lazyLoader)
|
||||||
{
|
{
|
||||||
await lazyLoader.SetText("Requesting server data");
|
await lazyLoader.SetText("Requesting server data");
|
||||||
|
|
||||||
try
|
if (!Guid.TryParse(ServerUuid, out var uuid))
|
||||||
{
|
return;
|
||||||
var uuid = Guid.Parse(ServerUuid);
|
|
||||||
|
|
||||||
CurrentServer = ServerRepository
|
CurrentServer = ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.Allocations)
|
.Include(x => x.Allocations)
|
||||||
.Include(x => x.Image)
|
.Include(x => x.Image)
|
||||||
|
.Include("Image.Variables")
|
||||||
.Include(x => x.Node)
|
.Include(x => x.Node)
|
||||||
.Include(x => x.Variables)
|
.Include(x => x.Variables)
|
||||||
.Include(x => x.MainAllocation)
|
.Include(x => x.MainAllocation)
|
||||||
.Include(x => x.Owner)
|
.Include(x => x.Owner)
|
||||||
.First(x => x.Uuid == uuid);
|
.First(x => x.Uuid == uuid);
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentServer != null)
|
if (CurrentServer != null)
|
||||||
{
|
{
|
||||||
if (CurrentServer.Owner.Id != IdentityService.User.Id && !IdentityService.User.Admin)
|
if (CurrentServer.Owner.Id != IdentityService.User.Id && !IdentityService.User.Admin)
|
||||||
|
{
|
||||||
CurrentServer = null;
|
CurrentServer = null;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentServer != null)
|
if (string.IsNullOrEmpty(CurrentServer.Image.BackgroundImageUrl))
|
||||||
{
|
await DynamicBackgroundService.Reset();
|
||||||
|
else
|
||||||
|
await DynamicBackgroundService.Change(CurrentServer.Image.BackgroundImageUrl);
|
||||||
|
|
||||||
await lazyLoader.SetText("Checking node online status");
|
await lazyLoader.SetText("Checking node online status");
|
||||||
|
|
||||||
NodeOnline = await ServerService.IsHostUp(CurrentServer);
|
NodeOnline = await ServerService.IsHostUp(CurrentServer);
|
||||||
|
|
||||||
if (NodeOnline)
|
if (!NodeOnline)
|
||||||
{
|
return;
|
||||||
await lazyLoader.SetText("Checking server variables");
|
|
||||||
|
|
||||||
var image = ImageRepository
|
await lazyLoader.SetText("Checking server variables");
|
||||||
.Get()
|
|
||||||
.Include(x => x.Variables)
|
|
||||||
.First(x => x.Id == CurrentServer.Image.Id);
|
|
||||||
|
|
||||||
// Live variable migration
|
// Live variable migration
|
||||||
|
|
||||||
foreach (var variable in image.Variables)
|
foreach (var variable in CurrentServer.Image.Variables)
|
||||||
{
|
{
|
||||||
if (!CurrentServer.Variables.Any(x => x.Key == variable.Key))
|
if (CurrentServer.Variables.All(x => x.Key != variable.Key))
|
||||||
{
|
{
|
||||||
CurrentServer.Variables.Add(new ServerVariable()
|
CurrentServer.Variables.Add(new ServerVariable()
|
||||||
{
|
{
|
||||||
|
@ -246,15 +232,86 @@
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
|
|
||||||
await lazyLoader.SetText("Requesting tags");
|
await lazyLoader.SetText("Reading tags");
|
||||||
|
|
||||||
Tags = JsonConvert.DeserializeObject<string[]>(image.TagsJson) ?? Array.Empty<string>();
|
Tags = JsonConvert.DeserializeObject<string[]>(CurrentServer.Image.TagsJson) ?? Array.Empty<string>();
|
||||||
Image = image;
|
|
||||||
|
// Build server pages and settings
|
||||||
|
|
||||||
|
Context = new ServerPageContext()
|
||||||
|
{
|
||||||
|
Server = CurrentServer,
|
||||||
|
User = IdentityService.User,
|
||||||
|
ImageTags = Tags
|
||||||
|
};
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Console",
|
||||||
|
Route = "/",
|
||||||
|
Icon = "terminal",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerConsole))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Files",
|
||||||
|
Route = "/files",
|
||||||
|
Icon = "folder",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerFiles))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Backups",
|
||||||
|
Route = "/backups",
|
||||||
|
Icon = "box",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerBackups))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Network",
|
||||||
|
Route = "/network",
|
||||||
|
Icon = "wifi",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerNetwork))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Settings",
|
||||||
|
Route = "/settings",
|
||||||
|
Icon = "cog",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerSettings))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add default settings
|
||||||
|
|
||||||
|
Context.Settings.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Rename",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerRenameSetting))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Settings.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Reset",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerResetSetting))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Settings.Add(new()
|
||||||
|
{
|
||||||
|
Name = "Delete",
|
||||||
|
Component = ComponentHelper.FromType(typeof(ServerDeleteSetting))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context = await PluginService.BuildServerPage(Context);
|
||||||
|
|
||||||
await lazyLoader.SetText("Connecting to console");
|
await lazyLoader.SetText("Connecting to console");
|
||||||
|
|
||||||
await ReconnectConsole();
|
await ReconnectConsole();
|
||||||
|
|
||||||
|
// Register event system
|
||||||
|
|
||||||
await Event.On<Server>($"server.{CurrentServer.Uuid}.installComplete", this, server =>
|
await Event.On<Server>($"server.{CurrentServer.Uuid}.installComplete", this, server =>
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
|
@ -268,12 +325,6 @@
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Image.BackgroundImageUrl))
|
|
||||||
await DynamicBackgroundService.Reset();
|
|
||||||
else
|
|
||||||
await DynamicBackgroundService.Change(Image.BackgroundImageUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Helpers.Wings
|
@using Moonlight.App.Helpers.Wings
|
||||||
@using Moonlight.App.Helpers.Wings.Enums
|
@using Moonlight.App.Helpers.Wings.Enums
|
||||||
|
@using Moonlight.App.Plugin.UI
|
||||||
|
@using Moonlight.App.Plugin.UI.Servers
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|
||||||
@inject SmartTranslateService TranslationService
|
@inject SmartTranslateService TranslationService
|
||||||
|
@ -122,66 +124,19 @@
|
||||||
<div class="mt-5 row">
|
<div class="mt-5 row">
|
||||||
<div class="d-flex flex-column flex-md-row card card-body p-5">
|
<div class="d-flex flex-column flex-md-row card card-body p-5">
|
||||||
<ul class="nav nav-tabs nav-pills flex-row border-0 flex-md-column fs-6 pe-5 mb-5">
|
<ul class="nav nav-tabs nav-pills flex-row border-0 flex-md-column fs-6 pe-5 mb-5">
|
||||||
|
@foreach (var tab in Context.Tabs)
|
||||||
|
{
|
||||||
<li class="nav-item w-100 me-0 mb-md-2">
|
<li class="nav-item w-100 me-0 mb-md-2">
|
||||||
<a href="/server/@(CurrentServer.Uuid)/" class="nav-link w-100 btn btn-flex @(Index == 0 ? "active" : "") btn-active-light-primary">
|
<a href="/server/@(CurrentServer.Uuid + tab.Route)" class="nav-link w-100 btn btn-flex @(Route == tab.Route ? "active" : "") btn-active-light-primary">
|
||||||
<i class="bx bx-terminal bx-sm me-2"></i>
|
<i class="bx bx-@(tab.Icon) bx-sm me-2"></i>
|
||||||
<span class="d-flex flex-column align-items-start">
|
<span class="d-flex flex-column align-items-start">
|
||||||
<span class="fs-5">
|
<span class="fs-5">
|
||||||
<TL>Console</TL>
|
<TL>@(tab.Name)</TL>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item w-100 me-0 mb-md-2">
|
|
||||||
<a href="/server/@(CurrentServer.Uuid)/files" class="nav-link w-100 btn btn-flex @(Index == 1 ? "active" : "") btn-active-light-primary">
|
|
||||||
<i class="bx bx-folder bx-sm me-2"></i>
|
|
||||||
<span class="d-flex flex-column align-items-start">
|
|
||||||
<span class="fs-5">
|
|
||||||
<TL>Files</TL>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item w-100 me-0 mb-md-2">
|
|
||||||
<a href="/server/@(CurrentServer.Uuid)/backups" class="nav-link w-100 btn btn-flex @(Index == 2 ? "active" : "") btn-active-light-primary">
|
|
||||||
<i class="bx bx-box bx-sm me-2"></i>
|
|
||||||
<span class="d-flex flex-column align-items-start">
|
|
||||||
<span class="fs-5">
|
|
||||||
<TL>Backups</TL>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item w-100 me-0 mb-md-2">
|
|
||||||
<a href="/server/@(CurrentServer.Uuid)/network" class="nav-link w-100 btn btn-flex @(Index == 3 ? "active" : "") btn-active-light-primary">
|
|
||||||
<i class="bx bx-wifi bx-sm me-2"></i>
|
|
||||||
<span class="d-flex flex-column align-items-start">
|
|
||||||
<span class="fs-5">
|
|
||||||
<TL>Network</TL>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item w-100 me-0 mb-md-2">
|
|
||||||
<a href="/server/@(CurrentServer.Uuid)/addons" class="nav-link w-100 btn btn-flex @(Index == 4 ? "active" : "") btn-active-light-primary">
|
|
||||||
<i class="bx bx-plug bx-sm me-2"></i>
|
|
||||||
<span class="d-flex flex-column align-items-start">
|
|
||||||
<span class="fs-5">
|
|
||||||
<TL>Addons</TL>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item w-100 me-0 mb-md-2">
|
|
||||||
<a href="/server/@(CurrentServer.Uuid)/settings" class="nav-link w-100 btn btn-flex @(Index == 5 ? "active" : "") btn-active-light-primary">
|
|
||||||
<i class="bx bx-cog bx-sm me-2"></i>
|
|
||||||
<span class="d-flex flex-column align-items-start">
|
|
||||||
<span class="fs-5">
|
|
||||||
<TL>Settings</TL>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content w-100">
|
<div class="tab-content w-100">
|
||||||
<div class="tab-pane fade show active">
|
<div class="tab-pane fade show active">
|
||||||
|
@ -198,16 +153,17 @@
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Server CurrentServer { get; set; }
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public WingsConsole Console { get; set; }
|
public WingsConsole Console { get; set; }
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
public ServerPageContext Context { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment ChildContent { get; set; }
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int Index { get; set; } = 0;
|
public string Route { get; set; } = "/";
|
||||||
|
|
||||||
//TODO: NodeIpService which loads and caches raw ips for nodes (maybe)
|
//TODO: NodeIpService which loads and caches raw ips for nodes (maybe)
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Plugin.UI.Servers
|
||||||
@using Moonlight.Shared.Views.Server.Settings
|
|
||||||
@using Microsoft.AspNetCore.Components.Rendering
|
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@foreach (var setting in Settings)
|
@foreach (var setting in Context.Settings)
|
||||||
{
|
{
|
||||||
<div class="col-12 col-md-6 p-3">
|
<div class="col-12 col-md-6 p-3">
|
||||||
<div class="accordion" id="serverSetting@(setting.GetHashCode())">
|
<div class="accordion" id="serverSetting@(setting.GetHashCode())">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="serverSetting-header@(setting.GetHashCode())">
|
<h2 class="accordion-header" id="serverSetting-header@(setting.GetHashCode())">
|
||||||
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#serverSetting-body@(setting.GetHashCode())" aria-expanded="false" aria-controls="serverSetting-body@(setting.GetHashCode())">
|
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#serverSetting-body@(setting.GetHashCode())" aria-expanded="false" aria-controls="serverSetting-body@(setting.GetHashCode())">
|
||||||
<TL>@(setting.Key)</TL>
|
<TL>@(setting.Name)</TL>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="serverSetting-body@(setting.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverSetting-header@(setting.GetHashCode())" data-bs-parent="#serverSetting">
|
<div id="serverSetting-body@(setting.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverSetting-header@(setting.GetHashCode())" data-bs-parent="#serverSetting">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
@(GetComponent(setting.Value))
|
@(setting.Component)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,68 +21,9 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Server CurrentServer { get; set; }
|
public ServerPageContext Context { get; set; }
|
||||||
|
|
||||||
[CascadingParameter]
|
|
||||||
public string[] Tags { get; set; }
|
|
||||||
|
|
||||||
private Dictionary<string, Type> Settings = new();
|
|
||||||
|
|
||||||
private Task Load(LazyLoader lazyLoader)
|
|
||||||
{
|
|
||||||
if (Tags.Contains("paperversion"))
|
|
||||||
Settings.Add("Paper version", typeof(PaperVersionSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("forgeversion"))
|
|
||||||
Settings.Add("Forge version", typeof(ForgeVersionSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("fabricversion"))
|
|
||||||
Settings.Add("Fabric version", typeof(FabricVersionSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("join2start"))
|
|
||||||
Settings.Add("Join2Start", typeof(Join2StartSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("javascriptversion"))
|
|
||||||
Settings.Add("Javascript version", typeof(JavascriptVersionSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("javascriptfile"))
|
|
||||||
Settings.Add("Javascript file", typeof(JavascriptFileSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("pythonversion"))
|
|
||||||
Settings.Add("Python version", typeof(PythonVersionSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("javaversion"))
|
|
||||||
Settings.Add("Java version", typeof(JavaRuntimeVersionSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("dotnetversion"))
|
|
||||||
Settings.Add("Dotnet version", typeof(DotnetVersionSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("pythonfile"))
|
|
||||||
Settings.Add("Python file", typeof(PythonFileSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("javafile"))
|
|
||||||
Settings.Add("Jar file", typeof(JavaFileSetting));
|
|
||||||
|
|
||||||
if (Tags.Contains("dotnetfile"))
|
|
||||||
Settings.Add("Dll file", typeof(DotnetFileSetting));
|
|
||||||
|
|
||||||
Settings.Add("Rename", typeof(ServerRenameSetting));
|
|
||||||
|
|
||||||
Settings.Add("Reset", typeof(ServerResetSetting));
|
|
||||||
|
|
||||||
Settings.Add("Delete", typeof(ServerDeleteSetting));
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment GetComponent(Type type) => builder =>
|
|
||||||
{
|
|
||||||
builder.OpenComponent(0, type);
|
|
||||||
builder.CloseComponent();
|
|
||||||
};
|
|
||||||
}
|
}
|
|
@ -4,10 +4,14 @@
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.Shared.Components.WebsiteControl
|
@using Moonlight.Shared.Components.WebsiteControl
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Plugin.UI.Webspaces
|
||||||
|
@using Moonlight.App.Services.Plugins
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|
||||||
@inject Repository<WebSpace> WebSpaceRepository
|
@inject Repository<WebSpace> WebSpaceRepository
|
||||||
@inject WebSpaceService WebSpaceService
|
@inject WebSpaceService WebSpaceService
|
||||||
|
@inject PluginService PluginService
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
|
@ -32,43 +36,17 @@
|
||||||
if (HostOnline)
|
if (HostOnline)
|
||||||
{
|
{
|
||||||
<CascadingValue Value="CurrentWebspace">
|
<CascadingValue Value="CurrentWebspace">
|
||||||
@{
|
<CascadingValue Value="Context">
|
||||||
var index = 0;
|
<SmartRouter Route="@(Route)">
|
||||||
|
@foreach (var tab in Context.Tabs)
|
||||||
switch (Route)
|
|
||||||
{
|
{
|
||||||
case "files":
|
<Route Path="@(tab.Route)">
|
||||||
index = 1;
|
<WebSpaceNavigation Route="@(tab.Route)" WebSpace="CurrentWebspace"/>
|
||||||
break;
|
@(tab.Component)
|
||||||
case "sftp":
|
</Route>
|
||||||
index = 2;
|
|
||||||
break;
|
|
||||||
case "databases":
|
|
||||||
index = 3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
index = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
<WebSpaceNavigation Index="index" WebSpace="CurrentWebspace" />
|
|
||||||
|
|
||||||
@switch (Route)
|
|
||||||
{
|
|
||||||
case "files":
|
|
||||||
<WebSpaceFiles />
|
|
||||||
break;
|
|
||||||
case "sftp":
|
|
||||||
<WebSpaceSftp />
|
|
||||||
break;
|
|
||||||
case "databases":
|
|
||||||
<WebSpaceDatabases />
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
<WebSpaceDashboard />
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
</SmartRouter>
|
||||||
|
</CascadingValue>
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -101,6 +79,8 @@
|
||||||
private WebSpace? CurrentWebspace;
|
private WebSpace? CurrentWebspace;
|
||||||
private bool HostOnline = false;
|
private bool HostOnline = false;
|
||||||
|
|
||||||
|
private WebspacePageContext Context;
|
||||||
|
|
||||||
private async Task Load(LazyLoader lazyLoader)
|
private async Task Load(LazyLoader lazyLoader)
|
||||||
{
|
{
|
||||||
CurrentWebspace = WebSpaceRepository
|
CurrentWebspace = WebSpaceRepository
|
||||||
|
@ -112,14 +92,53 @@
|
||||||
if (CurrentWebspace != null)
|
if (CurrentWebspace != null)
|
||||||
{
|
{
|
||||||
if (CurrentWebspace.Owner.Id != IdentityService.User.Id && !IdentityService.User.Admin)
|
if (CurrentWebspace.Owner.Id != IdentityService.User.Id && !IdentityService.User.Admin)
|
||||||
|
{
|
||||||
CurrentWebspace = null;
|
CurrentWebspace = null;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentWebspace != null)
|
|
||||||
{
|
|
||||||
await lazyLoader.SetText("Checking host system online status");
|
await lazyLoader.SetText("Checking host system online status");
|
||||||
|
|
||||||
HostOnline = await WebSpaceService.IsHostUp(CurrentWebspace);
|
HostOnline = await WebSpaceService.IsHostUp(CurrentWebspace);
|
||||||
|
|
||||||
|
if (!HostOnline)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Context = new WebspacePageContext()
|
||||||
|
{
|
||||||
|
WebSpace = CurrentWebspace,
|
||||||
|
User = IdentityService.User
|
||||||
|
};
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Route = "/",
|
||||||
|
Name = "Dashboard",
|
||||||
|
Component = ComponentHelper.FromType(typeof(WebSpaceDashboard))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Route = "/files",
|
||||||
|
Name = "Files",
|
||||||
|
Component = ComponentHelper.FromType(typeof(WebSpaceFiles))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Route = "/sftp",
|
||||||
|
Name = "SFTP",
|
||||||
|
Component = ComponentHelper.FromType(typeof(WebSpaceSftp))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context.Tabs.Add(new()
|
||||||
|
{
|
||||||
|
Route = "/databases",
|
||||||
|
Name = "Databases",
|
||||||
|
Component = ComponentHelper.FromType(typeof(WebSpaceDatabases))
|
||||||
|
});
|
||||||
|
|
||||||
|
Context = await PluginService.BuildWebspacePage(Context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue