From 21bea974a9f2c3f7ae37ae932491139c08126202 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Sat, 22 Jul 2023 23:44:45 +0200 Subject: [PATCH] Implemented a basic plugin system --- Moonlight/App/Helpers/ComponentHelper.cs | 12 + Moonlight/App/Plugin/MoonlightPlugin.cs | 15 + .../Plugin/UI/Servers/ServerPageContext.cs | 11 + .../App/Plugin/UI/Servers/ServerSetting.cs | 9 + Moonlight/App/Plugin/UI/Servers/ServerTab.cs | 11 + .../UI/Webspaces/WebspacePageContext.cs | 10 + .../App/Plugin/UI/Webspaces/WebspaceTab.cs | 10 + Moonlight/App/Services/PluginService.cs | 69 +++++ Moonlight/Moonlight.csproj | 1 + Moonlight/Program.cs | 2 + .../WebsiteControl/WebSpaceNavigation.razor | 34 +-- Moonlight/Shared/Views/Server/Index.razor | 259 +++++++++++------- .../Views/Server/ServerNavigation.razor | 80 ++---- .../Shared/Views/Server/ServerSettings.razor | 100 ++----- Moonlight/Shared/Views/Webspace/Index.razor | 98 ++++--- 15 files changed, 412 insertions(+), 309 deletions(-) create mode 100644 Moonlight/App/Helpers/ComponentHelper.cs create mode 100644 Moonlight/App/Plugin/MoonlightPlugin.cs create mode 100644 Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs create mode 100644 Moonlight/App/Plugin/UI/Servers/ServerSetting.cs create mode 100644 Moonlight/App/Plugin/UI/Servers/ServerTab.cs create mode 100644 Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs create mode 100644 Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs create mode 100644 Moonlight/App/Services/PluginService.cs diff --git a/Moonlight/App/Helpers/ComponentHelper.cs b/Moonlight/App/Helpers/ComponentHelper.cs new file mode 100644 index 00000000..830c5958 --- /dev/null +++ b/Moonlight/App/Helpers/ComponentHelper.cs @@ -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(); + }; +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/MoonlightPlugin.cs b/Moonlight/App/Plugin/MoonlightPlugin.cs new file mode 100644 index 00000000..702dddc7 --- /dev/null +++ b/Moonlight/App/Plugin/MoonlightPlugin.cs @@ -0,0 +1,15 @@ +using Moonlight.App.Plugin.UI; +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? OnBuildServerPage { get; set; } + public Func? OnBuildWebspacePage { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs b/Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs new file mode 100644 index 00000000..0905be1d --- /dev/null +++ b/Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs @@ -0,0 +1,11 @@ +using Moonlight.App.Database.Entities; + +namespace Moonlight.App.Plugin.UI.Servers; + +public class ServerPageContext +{ + public List Tabs { get; set; } = new(); + public List Settings { get; set; } = new(); + public Server Server { get; set; } + public User User { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Servers/ServerSetting.cs b/Moonlight/App/Plugin/UI/Servers/ServerSetting.cs new file mode 100644 index 00000000..b032c66d --- /dev/null +++ b/Moonlight/App/Plugin/UI/Servers/ServerSetting.cs @@ -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; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Servers/ServerTab.cs b/Moonlight/App/Plugin/UI/Servers/ServerTab.cs new file mode 100644 index 00000000..8a31e6f9 --- /dev/null +++ b/Moonlight/App/Plugin/UI/Servers/ServerTab.cs @@ -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; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs b/Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs new file mode 100644 index 00000000..a8b8d700 --- /dev/null +++ b/Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs @@ -0,0 +1,10 @@ +using Moonlight.App.Database.Entities; + +namespace Moonlight.App.Plugin.UI.Webspaces; + +public class WebspacePageContext +{ + public List Tabs { get; set; } = new(); + public User User { get; set; } + public WebSpace WebSpace { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs b/Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs new file mode 100644 index 00000000..70d02c88 --- /dev/null +++ b/Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs @@ -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; } +} \ No newline at end of file diff --git a/Moonlight/App/Services/PluginService.cs b/Moonlight/App/Services/PluginService.cs new file mode 100644 index 00000000..fb4f04eb --- /dev/null +++ b/Moonlight/App/Services/PluginService.cs @@ -0,0 +1,69 @@ +using System.Reflection; +using Moonlight.App.Database.Entities; +using Moonlight.App.Helpers; +using Moonlight.App.Plugin; +using Moonlight.App.Plugin.UI; +using Moonlight.App.Plugin.UI.Servers; +using Moonlight.App.Plugin.UI.Webspaces; + +namespace Moonlight.App.Services; + +public class PluginService +{ + public List Plugins { get; set; } + + public PluginService() + { + LoadPlugins(); + } + + private void LoadPlugins() + { + Plugins = new(); + + var pluginType = typeof(MoonlightPlugin); + + foreach (var pluginFile in Directory.EnumerateFiles( + PathBuilder.Dir(Directory.GetCurrentDirectory(), "storage", "plugins")) + .Where(x => x.EndsWith(".dll"))) + { + var assembly = Assembly.LoadFile(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); + } + } + } + + Logger.Info($"Loaded {Plugins.Count} plugins"); + } + + public async Task BuildServerPage(ServerPageContext context) + { + foreach (var plugin in Plugins) + { + if (plugin.OnBuildServerPage != null) + await plugin.OnBuildServerPage.Invoke(context); + } + + return context; + } + + public async Task BuildWebspacePage(WebspacePageContext context) + { + foreach (var plugin in Plugins) + { + if (plugin.OnBuildWebspacePage != null) + await plugin.OnBuildWebspacePage.Invoke(context); + } + + return context; + } +} \ No newline at end of file diff --git a/Moonlight/Moonlight.csproj b/Moonlight/Moonlight.csproj index e8a71f2d..d8410558 100644 --- a/Moonlight/Moonlight.csproj +++ b/Moonlight/Moonlight.csproj @@ -90,6 +90,7 @@ + diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index bd2c59ec..4f504771 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -239,6 +239,7 @@ namespace Moonlight builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Other builder.Services.AddSingleton(); @@ -289,6 +290,7 @@ namespace Moonlight _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); + _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); diff --git a/Moonlight/Shared/Components/WebsiteControl/WebSpaceNavigation.razor b/Moonlight/Shared/Components/WebsiteControl/WebSpaceNavigation.razor index 92c0f2f8..6bce8879 100644 --- a/Moonlight/Shared/Components/WebsiteControl/WebSpaceNavigation.razor +++ b/Moonlight/Shared/Components/WebsiteControl/WebSpaceNavigation.razor @@ -1,4 +1,5 @@ @using Moonlight.App.Database.Entities +@using Moonlight.App.Plugin.UI.Webspaces @using Moonlight.App.Services @using Moonlight.App.Services.Interop @@ -34,26 +35,14 @@
@@ -61,10 +50,13 @@ @code { [Parameter] - public int Index { get; set; } + public string Route { get; set; } [Parameter] public WebSpace WebSpace { get; set; } + + [CascadingParameter] + public WebspacePageContext Context { get; set; } private async Task Delete() { diff --git a/Moonlight/Shared/Views/Server/Index.razor b/Moonlight/Shared/Views/Server/Index.razor index d91238e8..3e0b4689 100644 --- a/Moonlight/Shared/Views/Server/Index.razor +++ b/Moonlight/Shared/Views/Server/Index.razor @@ -4,12 +4,16 @@ @using Microsoft.EntityFrameworkCore @using Moonlight.App.Database.Entities @using Moonlight.App.Events +@using Moonlight.App.Helpers @using Moonlight.App.Helpers.Wings @using Moonlight.App.Helpers.Wings.Enums +@using Moonlight.App.Plugin.UI +@using Moonlight.App.Plugin.UI.Servers @using Moonlight.App.Repositories @using Moonlight.App.Services @using Moonlight.App.Services.Sessions @using Moonlight.Shared.Components.Xterm +@using Moonlight.Shared.Views.Server.Settings @using Newtonsoft.Json @inject ImageRepository ImageRepository @@ -21,10 +25,11 @@ @inject DynamicBackgroundService DynamicBackgroundService @inject SmartTranslateService SmartTranslateService @inject IdentityService IdentityService +@inject PluginService PluginService @implements IDisposable - + @if (CurrentServer == null) { @@ -33,7 +38,7 @@ { if (NodeOnline) { - if (Console.ConsoleState == ConsoleState.Connected) + if (Console != null && Console.ConsoleState == ConsoleState.Connected) { if (Console.ServerState == ServerState.Installing || CurrentServer.Installing) { @@ -75,38 +80,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + @foreach (var tab in Context.Tabs) + { + + + @(tab.Component) + + + } + + @@ -114,8 +99,17 @@ } else { -
- Connecting +
+
+
+

+ Connecting +

+

+ Connecting to the servers console to stream logs and the current resource usage +

+
+
} } @@ -144,21 +138,19 @@ [Parameter] public string ServerUuid { get; set; } - - [Parameter] public string? Route { get; set; } private WingsConsole? Console; private Server? CurrentServer; - private Node Node; private bool NodeOnline = false; - private Image Image; private NodeAllocation NodeAllocation; private string[] Tags; private Terminal? InstallConsole; + private ServerPageContext Context; + protected override void OnInitialized() { Console = new(); @@ -171,109 +163,166 @@ Console.OnMessage += async (_, s) => { - if (Console.ServerState == ServerState.Installing) + if (Console.ServerState == ServerState.Installing && InstallConsole != null) { - if (InstallConsole != null) - { - if (s.IsInternal) - await InstallConsole.WriteLine("\x1b[38;5;16;48;5;135m\x1b[39m\x1b[1m Moonlight \x1b[0m " + s.Content + "\x1b[0m"); - else - await InstallConsole.WriteLine(s.Content); - } + if (s.IsInternal) + await InstallConsole.WriteLine("\x1b[38;5;16;48;5;135m\x1b[39m\x1b[1m Moonlight \x1b[0m " + s.Content + "\x1b[0m"); + else + await InstallConsole.WriteLine(s.Content); } }; } - private async Task LoadData(LazyLoader lazyLoader) + private async Task Load(LazyLoader lazyLoader) { await lazyLoader.SetText("Requesting server data"); - try - { - var uuid = Guid.Parse(ServerUuid); + if (!Guid.TryParse(ServerUuid, out var uuid)) + return; - CurrentServer = ServerRepository - .Get() - .Include(x => x.Allocations) - .Include(x => x.Image) - .Include(x => x.Node) - .Include(x => x.Variables) - .Include(x => x.MainAllocation) - .Include(x => x.Owner) - .First(x => x.Uuid == uuid); - } - catch (Exception) - { - // ignored - } + CurrentServer = ServerRepository + .Get() + .Include(x => x.Allocations) + .Include(x => x.Image) + .Include("Image.Variables") + .Include(x => x.Node) + .Include(x => x.Variables) + .Include(x => x.MainAllocation) + .Include(x => x.Owner) + .First(x => x.Uuid == uuid); if (CurrentServer != null) { if (CurrentServer.Owner.Id != IdentityService.User.Id && !IdentityService.User.Admin) + { CurrentServer = null; - } + return; + } + + if (string.IsNullOrEmpty(CurrentServer.Image.BackgroundImageUrl)) + await DynamicBackgroundService.Reset(); + else + await DynamicBackgroundService.Change(CurrentServer.Image.BackgroundImageUrl); - if (CurrentServer != null) - { await lazyLoader.SetText("Checking node online status"); NodeOnline = await ServerService.IsHostUp(CurrentServer); - if (NodeOnline) - { - await lazyLoader.SetText("Checking server variables"); + if (!NodeOnline) + return; - var image = ImageRepository - .Get() - .Include(x => x.Variables) - .First(x => x.Id == CurrentServer.Image.Id); + await lazyLoader.SetText("Checking server variables"); // Live variable migration - foreach (var variable in image.Variables) + foreach (var variable in CurrentServer.Image.Variables) + { + if (CurrentServer.Variables.All(x => x.Key != variable.Key)) { - if (!CurrentServer.Variables.Any(x => x.Key == variable.Key)) + CurrentServer.Variables.Add(new ServerVariable() { - CurrentServer.Variables.Add(new ServerVariable() - { - Key = variable.Key, - Value = variable.DefaultValue - }); + Key = variable.Key, + Value = variable.DefaultValue + }); - ServerRepository.Update(CurrentServer); - } + ServerRepository.Update(CurrentServer); } + } // Tags - await lazyLoader.SetText("Requesting tags"); + await lazyLoader.SetText("Reading tags"); - Tags = JsonConvert.DeserializeObject(image.TagsJson) ?? Array.Empty(); - Image = image; + Tags = JsonConvert.DeserializeObject(CurrentServer.Image.TagsJson) ?? Array.Empty(); - await lazyLoader.SetText("Connecting to console"); + // Build server pages and settings - await ReconnectConsole(); + Context = new ServerPageContext() + { + Server = CurrentServer, + User = IdentityService.User + }; - await Event.On($"server.{CurrentServer.Uuid}.installComplete", this, server => - { - NavigationManager.NavigateTo(NavigationManager.Uri, true); + Context.Tabs.Add(new() + { + Name = "Console", + Route = "/", + Icon = "terminal", + Component = ComponentHelper.FromType(typeof(ServerConsole)) + }); - return Task.CompletedTask; - }); + Context.Tabs.Add(new() + { + Name = "Files", + Route = "/files", + Icon = "folder", + Component = ComponentHelper.FromType(typeof(ServerFiles)) + }); - await Event.On($"server.{CurrentServer.Uuid}.archiveStatusChanged", this, server => - { - NavigationManager.NavigateTo(NavigationManager.Uri, true); + Context.Tabs.Add(new() + { + Name = "Backups", + Route = "/backups", + Icon = "box", + Component = ComponentHelper.FromType(typeof(ServerBackups)) + }); - return Task.CompletedTask; - }); + Context.Tabs.Add(new() + { + Name = "Network", + Route = "/network", + Icon = "wifi", + Component = ComponentHelper.FromType(typeof(ServerNetwork)) + }); - if (string.IsNullOrEmpty(Image.BackgroundImageUrl)) - await DynamicBackgroundService.Reset(); - else - await DynamicBackgroundService.Change(Image.BackgroundImageUrl); - } + 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 ReconnectConsole(); + + // Register event system + + await Event.On($"server.{CurrentServer.Uuid}.installComplete", this, server => + { + NavigationManager.NavigateTo(NavigationManager.Uri, true); + + return Task.CompletedTask; + }); + + await Event.On($"server.{CurrentServer.Uuid}.archiveStatusChanged", this, server => + { + NavigationManager.NavigateTo(NavigationManager.Uri, true); + + return Task.CompletedTask; + }); } } diff --git a/Moonlight/Shared/Views/Server/ServerNavigation.razor b/Moonlight/Shared/Views/Server/ServerNavigation.razor index 7f84ce7c..ebb37a75 100644 --- a/Moonlight/Shared/Views/Server/ServerNavigation.razor +++ b/Moonlight/Shared/Views/Server/ServerNavigation.razor @@ -3,6 +3,8 @@ @using Moonlight.App.Helpers @using Moonlight.App.Helpers.Wings @using Moonlight.App.Helpers.Wings.Enums +@using Moonlight.App.Plugin.UI +@using Moonlight.App.Plugin.UI.Servers @using Moonlight.App.Services.Sessions @inject SmartTranslateService TranslationService @@ -122,66 +124,19 @@
@@ -198,16 +153,17 @@ [CascadingParameter] public Server CurrentServer { get; set; } - - [CascadingParameter] public WingsConsole Console { get; set; } + + [CascadingParameter] + public ServerPageContext Context { get; set; } [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] - public int Index { get; set; } = 0; + public string Route { get; set; } = "/"; //TODO: NodeIpService which loads and caches raw ips for nodes (maybe) diff --git a/Moonlight/Shared/Views/Server/ServerSettings.razor b/Moonlight/Shared/Views/Server/ServerSettings.razor index 25999848..82960044 100644 --- a/Moonlight/Shared/Views/Server/ServerSettings.razor +++ b/Moonlight/Shared/Views/Server/ServerSettings.razor @@ -1,91 +1,29 @@ -@using Moonlight.App.Database.Entities -@using Moonlight.Shared.Views.Server.Settings -@using Microsoft.AspNetCore.Components.Rendering +@using Moonlight.App.Plugin.UI.Servers - -
- @foreach (var setting in Settings) - { -
-
-
-

- -

-
-
- @(GetComponent(setting.Value)) -
+
+ @foreach (var setting in Context.Settings) + { +
+
+
+

+ +

+
+
+ @(setting.Component)
- } -
- +
+ } +
@code { [CascadingParameter] - public Server CurrentServer { get; set; } - - [CascadingParameter] - public string[] Tags { get; set; } - - private Dictionary 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(); - }; + public ServerPageContext Context { get; set; } } \ No newline at end of file diff --git a/Moonlight/Shared/Views/Webspace/Index.razor b/Moonlight/Shared/Views/Webspace/Index.razor index 1de867e0..d3e17f36 100644 --- a/Moonlight/Shared/Views/Webspace/Index.razor +++ b/Moonlight/Shared/Views/Webspace/Index.razor @@ -4,10 +4,13 @@ @using Moonlight.App.Services @using Moonlight.Shared.Components.WebsiteControl @using Microsoft.EntityFrameworkCore +@using Moonlight.App.Helpers +@using Moonlight.App.Plugin.UI.Webspaces @using Moonlight.App.Services.Sessions @inject Repository WebSpaceRepository @inject WebSpaceService WebSpaceService +@inject PluginService PluginService @inject IdentityService IdentityService @@ -32,43 +35,17 @@ if (HostOnline) { - @{ - var index = 0; - - switch (Route) - { - case "files": - index = 1; - break; - case "sftp": - index = 2; - break; - case "databases": - index = 3; - break; - default: - index = 0; - break; - } - - - - @switch (Route) - { - case "files": - - break; - case "sftp": - - break; - case "databases": - - break; - default: - - break; - } - } + + + @foreach (var tab in Context.Tabs) + { + + + @(tab.Component) + + } + + } else @@ -101,6 +78,8 @@ private WebSpace? CurrentWebspace; private bool HostOnline = false; + private WebspacePageContext Context; + private async Task Load(LazyLoader lazyLoader) { CurrentWebspace = WebSpaceRepository @@ -112,14 +91,53 @@ if (CurrentWebspace != null) { if (CurrentWebspace.Owner.Id != IdentityService.User.Id && !IdentityService.User.Admin) + { CurrentWebspace = null; - } + return; + } - if (CurrentWebspace != null) - { await lazyLoader.SetText("Checking host system online status"); 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); } } } \ No newline at end of file