Merge pull request #236 from Moonlight-Panel/AddPluginSystem
Add plugin system
This commit is contained in:
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; }
|
||||
}
|
||||
@@ -15,6 +15,13 @@ public static class Permissions
|
||||
Name = "Admin Statistics",
|
||||
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()
|
||||
{
|
||||
|
||||
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", "backups"));
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
|
||||
|
||||
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ public class MoonlightService
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user