Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f9508f30b | ||
|
|
428e2668d3 | ||
|
|
c1cfb35c86 | ||
|
|
d6777c463e | ||
|
|
f9126bffe0 | ||
|
|
0488e83a38 | ||
|
|
d87ddc90e3 | ||
|
|
151bc82998 | ||
|
|
e4c21c74a5 | ||
|
|
13741a2be9 | ||
|
|
c866e89b72 | ||
|
|
8be93bc53c | ||
|
|
384b6a3e7d | ||
|
|
ba2de54c60 | ||
|
|
bd5567e24f | ||
|
|
b8e39824b5 | ||
|
|
d8c9bdbd8d | ||
|
|
80eb210af0 | ||
|
|
a295354549 | ||
|
|
749ea5dc8e | ||
|
|
f52b9e2951 | ||
|
|
d2dbb68967 | ||
|
|
d1c9009e9f | ||
|
|
d024a834f9 | ||
|
|
ab529991fd | ||
|
|
92705837ba | ||
|
|
609d5451f9 | ||
|
|
2bb2caeeed | ||
|
|
61db49bfb7 | ||
|
|
a75678d305 | ||
|
|
d418c91efa | ||
|
|
7f2da5a55d | ||
|
|
5e592ccdcb | ||
|
|
016f50fb1c | ||
|
|
fe21668a2b | ||
|
|
1aab86a317 | ||
|
|
243d23d4e2 | ||
|
|
2fe17473ae | ||
|
|
609cf8cfac | ||
|
|
678da30b09 | ||
|
|
d19412f4bb | ||
|
|
1665d6e537 | ||
|
|
fd210f2404 | ||
|
|
c33729fb44 | ||
|
|
7983bf3ee4 | ||
|
|
a09f60aea7 | ||
|
|
28b5893c21 | ||
|
|
25b47d8b6c | ||
|
|
85f5b8a7da | ||
|
|
ab9333f99a | ||
|
|
d60f8fc905 | ||
|
|
fe1f4412d8 | ||
|
|
f191533410 | ||
|
|
a894707536 | ||
|
|
d2ccc84286 | ||
|
|
f2ec43f2d2 | ||
|
|
7feccc8d9f | ||
|
|
a8bd1193ce | ||
|
|
366d1a9205 | ||
|
|
df9ed95c6b | ||
|
|
23a211362e | ||
|
|
cf91d44902 | ||
|
|
35633e21a9 | ||
|
|
ce8b8f6798 | ||
|
|
c28c80ba25 | ||
|
|
da17b1df93 | ||
|
|
f9f5865ef9 | ||
|
|
389ded9b77 | ||
|
|
faebaa59dd | ||
|
|
6b7dc2ad05 | ||
|
|
e356c9d0c8 |
@@ -47,7 +47,7 @@ public class ModrinthApiHelper
|
|||||||
|
|
||||||
var request = new RestRequest(url)
|
var request = new RestRequest(url)
|
||||||
{
|
{
|
||||||
Timeout = 60 * 15
|
Timeout = 300000
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
|||||||
11
Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs
Normal file
11
Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Moonlight.App.ApiClients.Telemetry.Requests;
|
||||||
|
|
||||||
|
public class TelemetryData
|
||||||
|
{
|
||||||
|
public string AppUrl { get; set; } = "";
|
||||||
|
public int Servers { get; set; }
|
||||||
|
public int Nodes { get; set; }
|
||||||
|
public int Users { get; set; }
|
||||||
|
public int Databases { get; set; }
|
||||||
|
public int Webspaces { get; set; }
|
||||||
|
}
|
||||||
52
Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs
Normal file
52
Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace Moonlight.App.ApiClients.Telemetry;
|
||||||
|
|
||||||
|
public class TelemetryApiHelper
|
||||||
|
{
|
||||||
|
private readonly RestClient Client;
|
||||||
|
|
||||||
|
public TelemetryApiHelper()
|
||||||
|
{
|
||||||
|
Client = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Post(string resource, object? body)
|
||||||
|
{
|
||||||
|
var request = CreateRequest(resource);
|
||||||
|
|
||||||
|
request.Method = Method.Post;
|
||||||
|
|
||||||
|
request.AddParameter("application/json", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||||
|
|
||||||
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
if (response.StatusCode != 0)
|
||||||
|
{
|
||||||
|
throw new TelemetryException(
|
||||||
|
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||||
|
(int)response.StatusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestRequest CreateRequest(string resource)
|
||||||
|
{
|
||||||
|
var url = "https://telemetry.moonlightpanel.xyz/" + resource;
|
||||||
|
|
||||||
|
var request = new RestRequest(url)
|
||||||
|
{
|
||||||
|
Timeout = 3000000
|
||||||
|
};
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Moonlight/App/ApiClients/Telemetry/TelemetryException.cs
Normal file
32
Moonlight/App/ApiClients/Telemetry/TelemetryException.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Moonlight.App.ApiClients.Telemetry;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class TelemetryException : Exception
|
||||||
|
{
|
||||||
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
|
public TelemetryException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryException(string message, int statusCode) : base(message)
|
||||||
|
{
|
||||||
|
StatusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryException(string message, Exception inner) : base(message, inner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TelemetryException(
|
||||||
|
SerializationInfo info,
|
||||||
|
StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -209,7 +209,7 @@ public class WingsApiHelper
|
|||||||
|
|
||||||
var request = new RestRequest(url)
|
var request = new RestRequest(url)
|
||||||
{
|
{
|
||||||
Timeout = 60 * 15
|
Timeout = 300000
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
|||||||
332
Moonlight/App/Configuration/ConfigV1.cs
Normal file
332
Moonlight/App/Configuration/ConfigV1.cs
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Configuration;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
public class ConfigV1
|
||||||
|
{
|
||||||
|
[JsonProperty("Moonlight")]
|
||||||
|
public MoonlightData Moonlight { get; set; } = new();
|
||||||
|
|
||||||
|
public class MoonlightData
|
||||||
|
{
|
||||||
|
[JsonProperty("AppUrl")]
|
||||||
|
[Description("The url moonlight is accesible with from the internet")]
|
||||||
|
public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
|
||||||
|
|
||||||
|
[JsonProperty("Auth")] public AuthData Auth { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("DiscordBotApi")] public DiscordBotApiData DiscordBotApi { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("DiscordBot")] public DiscordBotData DiscordBot { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Domains")] public DomainsData Domains { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Html")] public HtmlData Html { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Marketing")] public MarketingData Marketing { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("OAuth2")] public OAuth2Data OAuth2 { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Security")] public SecurityData Security { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Mail")] public MailData Mail { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Cleanup")] public CleanupData Cleanup { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Subscriptions")] public SubscriptionsData Subscriptions { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("DiscordNotifications")] public DiscordNotificationsData DiscordNotifications { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Rating")] public RatingData Rating { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("SmartDeploy")] public SmartDeployData SmartDeploy { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Sentry")] public SentryData Sentry { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AuthData
|
||||||
|
{
|
||||||
|
[JsonProperty("DenyLogin")]
|
||||||
|
[Description("Prevent every new login")]
|
||||||
|
public bool DenyLogin { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("DenyRegister")]
|
||||||
|
[Description("Prevent every new user to register")]
|
||||||
|
public bool DenyRegister { get; set; } = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CleanupData
|
||||||
|
{
|
||||||
|
[JsonProperty("Cpu")]
|
||||||
|
[Description("The maximum amount of cpu usage in percent a node is allowed to use before the cleanup starts")]
|
||||||
|
public long Cpu { get; set; } = 90;
|
||||||
|
|
||||||
|
[JsonProperty("Memory")]
|
||||||
|
[Description("The minumum amount of memory in megabytes avaliable before the cleanup starts")]
|
||||||
|
public long Memory { get; set; } = 8192;
|
||||||
|
|
||||||
|
[JsonProperty("Wait")]
|
||||||
|
[Description("The delay between every cleanup check in minutes")]
|
||||||
|
public long Wait { get; set; } = 15;
|
||||||
|
|
||||||
|
[JsonProperty("Uptime")]
|
||||||
|
[Description("The maximum uptime of any server in hours before it the server restarted by the cleanup system")]
|
||||||
|
public long Uptime { get; set; } = 6;
|
||||||
|
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("The cleanup system provides a fair way for stopping unused servers and staying stable even with overallocation. A detailed explanation: docs.endelon-hosting.de/erklaerungen/cleanup")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("MinUptime")]
|
||||||
|
[Description("The minumum uptime of a server in minutes to prevent stopping servers which just started")]
|
||||||
|
public long MinUptime { get; set; } = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DatabaseData
|
||||||
|
{
|
||||||
|
[JsonProperty("Database")] public string Database { get; set; } = "moonlight_db";
|
||||||
|
|
||||||
|
[JsonProperty("Host")] public string Host { get; set; } = "your.database.host";
|
||||||
|
|
||||||
|
[JsonProperty("Password")]
|
||||||
|
[Blur]
|
||||||
|
public string Password { get; set; } = "secret";
|
||||||
|
|
||||||
|
[JsonProperty("Port")] public long Port { get; set; } = 3306;
|
||||||
|
|
||||||
|
[JsonProperty("Username")] public string Username { get; set; } = "moonlight_user";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscordBotApiData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("Enable the discord bot api. Currently only DatBot is using this api")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Token")]
|
||||||
|
[Description("Specify the token the api client needs to provide")]
|
||||||
|
[Blur]
|
||||||
|
public string Token { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
|
public class DiscordBotData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("The discord bot can be used to allow customers to manage their servers via discord")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Token")]
|
||||||
|
[Description("Your discord bot token goes here")]
|
||||||
|
[Blur]
|
||||||
|
public string Token { get; set; } = "discord token here";
|
||||||
|
|
||||||
|
[JsonProperty("PowerActions")]
|
||||||
|
[Description("Enable actions like starting and stopping servers")]
|
||||||
|
public bool PowerActions { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("SendCommands")]
|
||||||
|
[Description("Allow users to send commands to their servers")]
|
||||||
|
public bool SendCommands { get; set; } = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscordNotificationsData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("The discord notification system sends you a message everytime a event like a new support chat message is triggered with usefull data describing the event")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("WebHook")]
|
||||||
|
[Description("The discord webhook the notifications are being sent to")]
|
||||||
|
[Blur]
|
||||||
|
public string WebHook { get; set; } = "http://your-discord-webhook-url";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DomainsData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("This enables the domain system")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("AccountId")]
|
||||||
|
[Description("This option specifies the cloudflare account id")]
|
||||||
|
public string AccountId { get; set; } = "cloudflare acc id";
|
||||||
|
|
||||||
|
[JsonProperty("Email")]
|
||||||
|
[Description("This specifies the cloudflare email to use for communicating with the cloudflare api")]
|
||||||
|
public string Email { get; set; } = "cloudflare@acc.email";
|
||||||
|
|
||||||
|
[JsonProperty("Key")]
|
||||||
|
[Description("Your cloudflare api key goes here")]
|
||||||
|
[Blur]
|
||||||
|
public string Key { get; set; } = "secret";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HtmlData
|
||||||
|
{
|
||||||
|
[JsonProperty("Headers")] public HeadersData Headers { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HeadersData
|
||||||
|
{
|
||||||
|
[JsonProperty("Color")]
|
||||||
|
[Description("This specifies the color of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
|
||||||
|
public string Color { get; set; } = "#4b27e8";
|
||||||
|
|
||||||
|
[JsonProperty("Description")]
|
||||||
|
[Description("This specifies the description text of the embed generated by platforms like discord when someone posts a link to your moonlight instance and can also help google to index your moonlight instance correctly")]
|
||||||
|
public string Description { get; set; } = "the next generation hosting panel";
|
||||||
|
|
||||||
|
[JsonProperty("Keywords")]
|
||||||
|
[Description("To help search engines like google to index your moonlight instance correctly you can specify keywords seperated by a comma here")]
|
||||||
|
public string Keywords { get; set; } = "moonlight";
|
||||||
|
|
||||||
|
[JsonProperty("Title")]
|
||||||
|
[Description("This specifies the title of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
|
||||||
|
public string Title { get; set; } = "Moonlight - endelon.link";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MailData
|
||||||
|
{
|
||||||
|
[JsonProperty("Email")] public string Email { get; set; } = "username@your.mail.host";
|
||||||
|
|
||||||
|
[JsonProperty("Server")] public string Server { get; set; } = "your.mail.host";
|
||||||
|
|
||||||
|
[JsonProperty("Password")]
|
||||||
|
[Blur]
|
||||||
|
public string Password { get; set; } = "secret";
|
||||||
|
|
||||||
|
[JsonProperty("Port")] public int Port { get; set; } = 465;
|
||||||
|
|
||||||
|
[JsonProperty("Ssl")] public bool Ssl { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MarketingData
|
||||||
|
{
|
||||||
|
[JsonProperty("BrandName")] public string BrandName { get; set; } = "Endelon Hosting";
|
||||||
|
|
||||||
|
[JsonProperty("Imprint")] public string Imprint { get; set; } = "https://your-site.xyz/imprint";
|
||||||
|
|
||||||
|
[JsonProperty("Privacy")] public string Privacy { get; set; } = "https://your-site.xyz/privacy";
|
||||||
|
[JsonProperty("About")] public string About { get; set; } = "https://your-site.xyz/about";
|
||||||
|
[JsonProperty("Website")] public string Website { get; set; } = "https://your-site.xyz";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OAuth2Data
|
||||||
|
{
|
||||||
|
[JsonProperty("OverrideUrl")]
|
||||||
|
[Description("This overrides the redirect url which would be typicaly the app url")]
|
||||||
|
public string OverrideUrl { get; set; } = "https://only-for-development.cases";
|
||||||
|
|
||||||
|
[JsonProperty("EnableOverrideUrl")]
|
||||||
|
[Description("This enables the url override")]
|
||||||
|
public bool EnableOverrideUrl { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Providers")]
|
||||||
|
public OAuth2ProviderData[] Providers { get; set; } = Array.Empty<OAuth2ProviderData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OAuth2ProviderData
|
||||||
|
{
|
||||||
|
[JsonProperty("Id")] public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("ClientId")] public string ClientId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("ClientSecret")]
|
||||||
|
[Blur]
|
||||||
|
public string ClientSecret { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RatingData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enabled")]
|
||||||
|
[Description("The rating systems shows a user who is registered longer than the set amout of days a popup to rate this platform if he hasnt rated it before")]
|
||||||
|
public bool Enabled { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Url")]
|
||||||
|
[Description("This is the url a user who rated above a set limit is shown to rate you again. Its recommended to put your google or trustpilot rate link here")]
|
||||||
|
public string Url { get; set; } = "https://link-to-google-or-smth";
|
||||||
|
|
||||||
|
[JsonProperty("MinRating")]
|
||||||
|
[Description("The minimum star count on the rating ranging from 1 to 5")]
|
||||||
|
public int MinRating { get; set; } = 4;
|
||||||
|
|
||||||
|
[JsonProperty("DaysSince")]
|
||||||
|
[Description("The days a user has to be registered to even be able to get this popup")]
|
||||||
|
public int DaysSince { get; set; } = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SecurityData
|
||||||
|
{
|
||||||
|
[JsonProperty("Token")]
|
||||||
|
[Description("This is the moonlight app token. It is used to encrypt and decrypt data and validte tokens and sessions")]
|
||||||
|
[Blur]
|
||||||
|
public string Token { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReCaptchaData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("Enables repatcha at places like the register page. For information how to get your recaptcha credentails go to google.com/recaptcha/about/")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("SiteKey")]
|
||||||
|
[Blur]
|
||||||
|
public string SiteKey { get; set; } = "recaptcha site key here";
|
||||||
|
|
||||||
|
[JsonProperty("SecretKey")]
|
||||||
|
[Blur]
|
||||||
|
public string SecretKey { get; set; } = "recaptcha secret here";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SentryData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("Sentry is a way to monitor application crashes and performance issues in real time. Enable this option only if you set a sentry dsn")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Dsn")]
|
||||||
|
[Description("The dsn is the key moonlight needs to communicate with your sentry instance")]
|
||||||
|
[Blur]
|
||||||
|
public string Dsn { get; set; } = "http://your-sentry-url-here";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SmartDeployData
|
||||||
|
{
|
||||||
|
[JsonProperty("Server")] public SmartDeployServerData Server { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SmartDeployServerData
|
||||||
|
{
|
||||||
|
[JsonProperty("EnableOverride")] public bool EnableOverride { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("OverrideNode")] public long OverrideNode { get; set; } = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticsData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enabled")] public bool Enabled { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Wait")] public long Wait { get; set; } = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubscriptionsData
|
||||||
|
{
|
||||||
|
[JsonProperty("SellPass")] public SellPassData SellPass { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SellPassData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Url")] public string Url { get; set; } = "https://not-implemented-yet";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,14 +52,14 @@ public class DataContext : DbContext
|
|||||||
if (!optionsBuilder.IsConfigured)
|
if (!optionsBuilder.IsConfigured)
|
||||||
{
|
{
|
||||||
var config = ConfigService
|
var config = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Database");
|
.Moonlight.Database;
|
||||||
|
|
||||||
var connectionString = $"host={config.GetValue<string>("Host")};" +
|
var connectionString = $"host={config.Host};" +
|
||||||
$"port={config.GetValue<int>("Port")};" +
|
$"port={config.Port};" +
|
||||||
$"database={config.GetValue<string>("Database")};" +
|
$"database={config.Database};" +
|
||||||
$"uid={config.GetValue<string>("Username")};" +
|
$"uid={config.Username};" +
|
||||||
$"pwd={config.GetValue<string>("Password")}";
|
$"pwd={config.Password}";
|
||||||
|
|
||||||
optionsBuilder.UseMySql(
|
optionsBuilder.UseMySql(
|
||||||
connectionString,
|
connectionString,
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public class User
|
|||||||
|
|
||||||
public string Country { get; set; } = "";
|
public string Country { get; set; } = "";
|
||||||
|
|
||||||
|
public string ServerListLayoutJson { get; set; } = "";
|
||||||
|
|
||||||
// States
|
// States
|
||||||
|
|
||||||
public UserStatus Status { get; set; } = UserStatus.Unverified;
|
public UserStatus Status { get; set; } = UserStatus.Unverified;
|
||||||
@@ -31,6 +33,7 @@ public class User
|
|||||||
public bool SupportPending { get; set; } = false;
|
public bool SupportPending { get; set; } = false;
|
||||||
public bool HasRated { get; set; } = false;
|
public bool HasRated { get; set; } = false;
|
||||||
public int Rating { get; set; } = 0;
|
public int Rating { get; set; } = 0;
|
||||||
|
public bool StreamerMode { get; set; } = false;
|
||||||
|
|
||||||
// Security
|
// Security
|
||||||
public bool TotpEnabled { get; set; } = false;
|
public bool TotpEnabled { get; set; } = false;
|
||||||
@@ -50,4 +53,8 @@ public class User
|
|||||||
public Subscription? CurrentSubscription { get; set; } = null;
|
public Subscription? CurrentSubscription { get; set; } = null;
|
||||||
public DateTime SubscriptionSince { get; set; } = DateTime.Now;
|
public DateTime SubscriptionSince { get; set; } = DateTime.Now;
|
||||||
public int SubscriptionDuration { get; set; }
|
public int SubscriptionDuration { get; set; }
|
||||||
|
|
||||||
|
// Ip logs
|
||||||
|
public string RegisterIp { get; set; } = "";
|
||||||
|
public string LastIp { get; set; } = "";
|
||||||
}
|
}
|
||||||
1077
Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs
generated
Normal file
1077
Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedServerListLayoutToUser : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ServerListLayoutJson",
|
||||||
|
table: "Users",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ServerListLayoutJson",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1080
Moonlight/App/Database/Migrations/20230625190428_AddedStreamerMode.Designer.cs
generated
Normal file
1080
Moonlight/App/Database/Migrations/20230625190428_AddedStreamerMode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedStreamerMode : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "StreamerMode",
|
||||||
|
table: "Users",
|
||||||
|
type: "tinyint(1)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "StreamerMode",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1088
Moonlight/App/Database/Migrations/20230703175432_AddedIpLogsForUser.Designer.cs
generated
Normal file
1088
Moonlight/App/Database/Migrations/20230703175432_AddedIpLogsForUser.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedIpLogsForUser : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "LastIp",
|
||||||
|
table: "Users",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "RegisterIp",
|
||||||
|
table: "Users",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "LastIp",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "RegisterIp",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -766,6 +766,10 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<bool>("HasRated")
|
b.Property<bool>("HasRated")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("LastIp")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
b.Property<string>("LastName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -780,6 +784,14 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<int>("Rating")
|
b.Property<int>("Rating")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("RegisterIp")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("ServerListLayoutJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("State")
|
b.Property<string>("State")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -787,6 +799,9 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<int>("Status")
|
b.Property<int>("Status")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("StreamerMode")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
b.Property<int>("SubscriptionDuration")
|
b.Property<int>("SubscriptionDuration")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
30
Moonlight/App/Extensions/JSRuntimeExtensions.cs
Normal file
30
Moonlight/App/Extensions/JSRuntimeExtensions.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Extensions;
|
||||||
|
|
||||||
|
public static class JSRuntimeExtensions
|
||||||
|
{
|
||||||
|
public static async Task InvokeVoidSafeAsync(this IJSRuntime jsRuntime, string method, params object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await jsRuntime.InvokeVoidAsync(method, args);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InvokeVoidSafe(this IJSRuntime jsRuntime, string method, params object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
jsRuntime.InvokeVoidAsync(method, args);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Moonlight/App/Helpers/BlurAttribute.cs
Normal file
6
Moonlight/App/Helpers/BlurAttribute.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class BlurAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -66,15 +66,13 @@ public class DatabaseCheckupService
|
|||||||
var configService = new ConfigService(new StorageService());
|
var configService = new ConfigService(new StorageService());
|
||||||
var dateTimeService = new DateTimeService();
|
var dateTimeService = new DateTimeService();
|
||||||
|
|
||||||
var config = configService
|
var config = configService.Get().Moonlight.Database;
|
||||||
.GetSection("Moonlight")
|
|
||||||
.GetSection("Database");
|
|
||||||
|
|
||||||
var connectionString = $"host={config.GetValue<string>("Host")};" +
|
var connectionString = $"host={config.Host};" +
|
||||||
$"port={config.GetValue<int>("Port")};" +
|
$"port={config.Port};" +
|
||||||
$"database={config.GetValue<string>("Database")};" +
|
$"database={config.Database};" +
|
||||||
$"uid={config.GetValue<string>("Username")};" +
|
$"uid={config.Username};" +
|
||||||
$"pwd={config.GetValue<string>("Password")}";
|
$"pwd={config.Password}";
|
||||||
|
|
||||||
string file = PathBuilder.File("storage", "backups", $"{dateTimeService.GetCurrentUnix()}-mysql.sql");
|
string file = PathBuilder.File("storage", "backups", $"{dateTimeService.GetCurrentUnix()}-mysql.sql");
|
||||||
|
|
||||||
|
|||||||
71
Moonlight/App/Helpers/EggConverter.cs
Normal file
71
Moonlight/App/Helpers/EggConverter.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public static class EggConverter
|
||||||
|
{
|
||||||
|
public static Image Convert(string json)
|
||||||
|
{
|
||||||
|
var result = new Image();
|
||||||
|
|
||||||
|
var data = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(json))
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
result.Allocations = 1;
|
||||||
|
result.Description = data.GetValue<string>("description") ?? "";
|
||||||
|
result.Uuid = Guid.NewGuid();
|
||||||
|
result.Startup = data.GetValue<string>("startup") ?? "";
|
||||||
|
result.Name = data.GetValue<string>("name") ?? "Ptero Egg";
|
||||||
|
|
||||||
|
foreach (var variable in data.GetSection("variables").GetChildren())
|
||||||
|
{
|
||||||
|
result.Variables.Add(new()
|
||||||
|
{
|
||||||
|
Key = variable.GetValue<string>("env_variable") ?? "",
|
||||||
|
DefaultValue = variable.GetValue<string>("default_value") ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var configData = data.GetSection("config");
|
||||||
|
|
||||||
|
result.ConfigFiles = configData.GetValue<string>("files") ?? "{}";
|
||||||
|
|
||||||
|
var dImagesData = JObject.Parse(json);
|
||||||
|
var dImages = (JObject)dImagesData["docker_images"]!;
|
||||||
|
|
||||||
|
foreach (var dockerImage in dImages)
|
||||||
|
{
|
||||||
|
var di = new DockerImage()
|
||||||
|
{
|
||||||
|
Default = dockerImage.Key == dImages.Properties().Last().Name,
|
||||||
|
Name = dockerImage.Value!.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
result.DockerImages.Add(di);
|
||||||
|
}
|
||||||
|
|
||||||
|
var installSection = data.GetSection("scripts").GetSection("installation");
|
||||||
|
|
||||||
|
result.InstallEntrypoint = installSection.GetValue<string>("entrypoint") ?? "bash";
|
||||||
|
result.InstallScript = installSection.GetValue<string>("script") ?? "";
|
||||||
|
result.InstallDockerImage = installSection.GetValue<string>("container") ?? "";
|
||||||
|
|
||||||
|
var rawJson = configData.GetValue<string>("startup");
|
||||||
|
|
||||||
|
var startupData = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(rawJson!))
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
result.StartupDetection = startupData.GetValue<string>("done", "") ?? "";
|
||||||
|
result.StopCommand = configData.GetValue<string>("stop") ?? "";
|
||||||
|
|
||||||
|
result.TagsJson = "[]";
|
||||||
|
result.BackgroundImageUrl = "";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -111,7 +111,7 @@ public class WingsFileAccess : FileAccess
|
|||||||
request.AddParameter("name", "files");
|
request.AddParameter("name", "files");
|
||||||
request.AddParameter("filename", name);
|
request.AddParameter("filename", name);
|
||||||
request.AddHeader("Content-Type", "multipart/form-data");
|
request.AddHeader("Content-Type", "multipart/form-data");
|
||||||
request.AddHeader("Origin", ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl"));
|
request.AddHeader("Origin", ConfigService.Get().Moonlight.AppUrl);
|
||||||
request.AddFile("files", () =>
|
request.AddFile("files", () =>
|
||||||
{
|
{
|
||||||
return new StreamProgressHelper(dataStream)
|
return new StreamProgressHelper(dataStream)
|
||||||
|
|||||||
@@ -1,9 +1,36 @@
|
|||||||
using Moonlight.App.Services;
|
using System.Text;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
public static class Formatter
|
public static class Formatter
|
||||||
{
|
{
|
||||||
|
public static string ReplaceEnd(string input, string substringToReplace, string newSubstring)
|
||||||
|
{
|
||||||
|
int lastIndexOfSubstring = input.LastIndexOf(substringToReplace);
|
||||||
|
if (lastIndexOfSubstring >= 0)
|
||||||
|
{
|
||||||
|
input = input.Remove(lastIndexOfSubstring, substringToReplace.Length).Insert(lastIndexOfSubstring, newSubstring);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
public static string ConvertCamelCaseToSpaces(string input)
|
||||||
|
{
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (char c in input)
|
||||||
|
{
|
||||||
|
if (char.IsUpper(c))
|
||||||
|
{
|
||||||
|
output.Append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.ToString().Trim();
|
||||||
|
}
|
||||||
public static string FormatUptime(double uptime)
|
public static string FormatUptime(double uptime)
|
||||||
{
|
{
|
||||||
TimeSpan t = TimeSpan.FromMilliseconds(uptime);
|
TimeSpan t = TimeSpan.FromMilliseconds(uptime);
|
||||||
|
|||||||
51
Moonlight/App/Helpers/PropBinder.cs
Normal file
51
Moonlight/App/Helpers/PropBinder.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class PropBinder
|
||||||
|
{
|
||||||
|
private PropertyInfo PropertyInfo;
|
||||||
|
private object DataObject;
|
||||||
|
|
||||||
|
public PropBinder(PropertyInfo propertyInfo, object dataObject)
|
||||||
|
{
|
||||||
|
PropertyInfo = propertyInfo;
|
||||||
|
DataObject = dataObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StringValue
|
||||||
|
{
|
||||||
|
get => (string)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IntValue
|
||||||
|
{
|
||||||
|
get => (int)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long LongValue
|
||||||
|
{
|
||||||
|
get => (long)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BoolValue
|
||||||
|
{
|
||||||
|
get => (bool)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime DateTimeValue
|
||||||
|
{
|
||||||
|
get => (DateTime)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double DoubleValue
|
||||||
|
{
|
||||||
|
get => (double)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
Moonlight/App/Helpers/Retry.cs
Normal file
66
Moonlight/App/Helpers/Retry.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class Retry
|
||||||
|
{
|
||||||
|
private List<Type> RetryExceptionTypes;
|
||||||
|
private List<Func<Exception, bool>> RetryFilters;
|
||||||
|
private int RetryTimes = 1;
|
||||||
|
|
||||||
|
public Retry()
|
||||||
|
{
|
||||||
|
RetryExceptionTypes = new();
|
||||||
|
RetryFilters = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Retry Times(int times)
|
||||||
|
{
|
||||||
|
RetryTimes = times;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Retry At(Func<Exception, bool> filter)
|
||||||
|
{
|
||||||
|
RetryFilters.Add(filter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Retry At<T>()
|
||||||
|
{
|
||||||
|
RetryExceptionTypes.Add(typeof(T));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Call(Func<Task> method)
|
||||||
|
{
|
||||||
|
int triesLeft = RetryTimes;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await method.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if(triesLeft < 1) // Throw if no tries left
|
||||||
|
throw;
|
||||||
|
|
||||||
|
if (RetryExceptionTypes.Any(x => x.FullName == e.GetType().FullName))
|
||||||
|
{
|
||||||
|
triesLeft--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RetryFilters.Any(x => x.Invoke(e)))
|
||||||
|
{
|
||||||
|
triesLeft--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw if not filtered -> unknown/unhandled
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} while (triesLeft >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -243,6 +243,7 @@ public class WingsConsole : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(JsonReaderException){}
|
catch(JsonReaderException){}
|
||||||
|
catch(JsonSerializationException){}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (!Disconnecting)
|
if (!Disconnecting)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class WingsConsoleHelper
|
|||||||
{
|
{
|
||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
|
|
||||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
AppUrl = configService.Get().Moonlight.AppUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectWings(WingsConsole console, Server server)
|
public async Task ConnectWings(WingsConsole console, Server server)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class WingsJwtHelper
|
|||||||
{
|
{
|
||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
|
|
||||||
AppUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
AppUrl = ConfigService.Get().Moonlight.AppUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Generate(string secret, Action<Dictionary<string, string>> claimsAction)
|
public string Generate(string secret, Action<Dictionary<string, string>> claimsAction)
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ public class DiscordBotController : Controller
|
|||||||
ServerService = serverService;
|
ServerService = serverService;
|
||||||
|
|
||||||
var config = configService
|
var config = configService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("DiscordBotApi");
|
.Moonlight.DiscordBotApi;
|
||||||
|
|
||||||
Enable = config.GetValue<bool>("Enable");
|
Enable = config.Enable;
|
||||||
|
|
||||||
if (Enable)
|
if (Enable)
|
||||||
{
|
{
|
||||||
Token = config.GetValue<string>("Token");
|
Token = config.Token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,19 +27,39 @@ public class LogMigrator : ILogger
|
|||||||
switch (logLevel)
|
switch (logLevel)
|
||||||
{
|
{
|
||||||
case LogLevel.Critical:
|
case LogLevel.Critical:
|
||||||
Logger.Fatal($"[{Name}] {formatter(state, exception)}");
|
Logger.Fatal(formatter(state, exception));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Fatal(exception);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case LogLevel.Warning:
|
case LogLevel.Warning:
|
||||||
Logger.Warn($"[{Name}] {formatter(state, exception)}");
|
Logger.Warn(formatter(state, exception));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Warn(exception);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case LogLevel.Debug:
|
case LogLevel.Debug:
|
||||||
Logger.Debug($"[{Name}] {formatter(state, exception)}");
|
Logger.Debug(formatter(state, exception));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Debug(exception);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case LogLevel.Error:
|
case LogLevel.Error:
|
||||||
Logger.Error($"[{Name}] {formatter(state, exception)}");
|
Logger.Error(formatter(state, exception));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Error(exception);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case LogLevel.Information:
|
case LogLevel.Information:
|
||||||
Logger.Info($"[{Name}] {formatter(state, exception)}");
|
Logger.Info(formatter(state, exception));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Info(exception);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
Moonlight/App/LogMigrator/SentryDiagnosticsLogger.cs
Normal file
71
Moonlight/App/LogMigrator/SentryDiagnosticsLogger.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Sentry;
|
||||||
|
using Sentry.Extensibility;
|
||||||
|
|
||||||
|
namespace Moonlight.App.LogMigrator;
|
||||||
|
|
||||||
|
public class SentryDiagnosticsLogger : IDiagnosticLogger
|
||||||
|
{
|
||||||
|
private readonly SentryLevel Level;
|
||||||
|
|
||||||
|
public SentryDiagnosticsLogger(SentryLevel level)
|
||||||
|
{
|
||||||
|
Level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(SentryLevel level)
|
||||||
|
{
|
||||||
|
if ((int)level >= (int)Level)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(SentryLevel logLevel, string message, Exception? exception = null, params object?[] args)
|
||||||
|
{
|
||||||
|
switch (logLevel)
|
||||||
|
{
|
||||||
|
case SentryLevel.Debug:
|
||||||
|
Logger.Debug(string.Format(message, args));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Debug(exception);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SentryLevel.Info:
|
||||||
|
Logger.Info(string.Format(message, args));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Info(exception);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SentryLevel.Warning:
|
||||||
|
Logger.Warn(string.Format(message, args));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Warn(exception);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SentryLevel.Error:
|
||||||
|
Logger.Error(string.Format(message, args));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Error(exception);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SentryLevel.Fatal:
|
||||||
|
Logger.Fatal(string.Format(message, args));
|
||||||
|
|
||||||
|
if(exception != null)
|
||||||
|
Logger.Fatal(exception);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Moonlight/App/Models/Forms/UserPreferencesDataModel.cs
Normal file
6
Moonlight/App/Models/Forms/UserPreferencesDataModel.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
|
public class UserPreferencesDataModel
|
||||||
|
{
|
||||||
|
public bool StreamerMode { get; set; } = false;
|
||||||
|
}
|
||||||
7
Moonlight/App/Models/Misc/ServerGroup.cs
Normal file
7
Moonlight/App/Models/Misc/ServerGroup.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public class ServerGroup
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public List<string> Servers { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Services.Interop;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
public class Session
|
|
||||||
{
|
|
||||||
public string Ip { get; set; } = "N/A";
|
|
||||||
public string Url { get; set; } = "N/A";
|
|
||||||
public string Device { get; set; } = "N/A";
|
|
||||||
public User? User { get; set; }
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
public NavigationManager Navigation { get; set; }
|
|
||||||
public AlertService AlertService { get; set; }
|
|
||||||
}
|
|
||||||
@@ -86,6 +86,13 @@ public class DiscordOAuth2Provider : OAuth2Provider
|
|||||||
|
|
||||||
var email = getData.GetValue<string>("email");
|
var email = getData.GetValue<string>("email");
|
||||||
var id = getData.GetValue<ulong>("id");
|
var id = getData.GetValue<ulong>("id");
|
||||||
|
var verified = getData.GetValue<bool>("verified");
|
||||||
|
|
||||||
|
if (!verified)
|
||||||
|
{
|
||||||
|
Logger.Warn("A user tried to use an unverified discord account to login", "security");
|
||||||
|
throw new DisplayException("You can only use verified discord accounts for oauth signin");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle data
|
// Handle data
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
using Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories;
|
|
||||||
|
|
||||||
public class SessionRepository
|
|
||||||
{
|
|
||||||
private readonly List<Session> Sessions;
|
|
||||||
|
|
||||||
public SessionRepository()
|
|
||||||
{
|
|
||||||
Sessions = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Session[] Get()
|
|
||||||
{
|
|
||||||
lock (Sessions)
|
|
||||||
{
|
|
||||||
return Sessions.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(Session session)
|
|
||||||
{
|
|
||||||
lock (Sessions)
|
|
||||||
{
|
|
||||||
Sessions.Add(session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(Session session)
|
|
||||||
{
|
|
||||||
lock (Sessions)
|
|
||||||
{
|
|
||||||
Sessions.Remove(session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,15 +43,15 @@ public class CleanupService
|
|||||||
CompletedAt = DateTimeService.GetCurrent();
|
CompletedAt = DateTimeService.GetCurrent();
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
var config = ConfigService.Get().Moonlight.Cleanup;
|
||||||
|
|
||||||
if (!config.GetValue<bool>("Enable") || ConfigService.DebugMode)
|
if (!config.Enable || ConfigService.DebugMode)
|
||||||
{
|
{
|
||||||
Logger.Info("Disabling cleanup service");
|
Logger.Info("Disabling cleanup service");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer = new(TimeSpan.FromMinutes(config.GetValue<int>("Wait")));
|
Timer = new(TimeSpan.FromMinutes(config.Wait));
|
||||||
|
|
||||||
Task.Run(Run);
|
Task.Run(Run);
|
||||||
}
|
}
|
||||||
@@ -63,12 +63,12 @@ public class CleanupService
|
|||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
|
|
||||||
using var scope = ServiceScopeFactory.CreateScope();
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
var config = ConfigService.Get().Moonlight.Cleanup;
|
||||||
|
|
||||||
var maxCpu = config.GetValue<int>("Cpu");
|
var maxCpu = config.Cpu;
|
||||||
var minMemory = config.GetValue<int>("Memory");
|
var minMemory = config.Memory;
|
||||||
var maxUptime = config.GetValue<int>("Uptime");
|
var maxUptime = config.Uptime;
|
||||||
var minUptime = config.GetValue<int>("MinUptime");
|
var minUptime = config.MinUptime;
|
||||||
|
|
||||||
var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>();
|
var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>();
|
||||||
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ public class DiscordNotificationService
|
|||||||
Event = eventSystem;
|
Event = eventSystem;
|
||||||
ResourceService = resourceService;
|
ResourceService = resourceService;
|
||||||
|
|
||||||
var config = configService.GetSection("Moonlight").GetSection("DiscordNotifications");
|
var config = configService.Get().Moonlight.DiscordNotifications;
|
||||||
|
|
||||||
if (config.GetValue<bool>("Enable"))
|
if (config.Enable)
|
||||||
{
|
{
|
||||||
Logger.Info("Discord notifications enabled");
|
Logger.Info("Discord notifications enabled");
|
||||||
|
|
||||||
Client = new(config.GetValue<string>("WebHook"));
|
Client = new(config.WebHook);
|
||||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
AppUrl = configService.Get().Moonlight.AppUrl;
|
||||||
|
|
||||||
Event.On<User>("supportChat.new", this, OnNewSupportChat);
|
Event.On<User>("supportChat.new", this, OnNewSupportChat);
|
||||||
Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage);
|
Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage);
|
||||||
|
|||||||
62
Moonlight/App/Services/Background/TelemetryService.cs
Normal file
62
Moonlight/App/Services/Background/TelemetryService.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using Moonlight.App.ApiClients.Telemetry;
|
||||||
|
using Moonlight.App.ApiClients.Telemetry.Requests;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Background;
|
||||||
|
|
||||||
|
public class TelemetryService
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
|
|
||||||
|
public TelemetryService(
|
||||||
|
ConfigService configService,
|
||||||
|
IServiceScopeFactory serviceScopeFactory)
|
||||||
|
{
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
ConfigService = configService;
|
||||||
|
|
||||||
|
if(!ConfigService.DebugMode)
|
||||||
|
Task.Run(Run);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Run()
|
||||||
|
{
|
||||||
|
var timer = new PeriodicTimer(TimeSpan.FromMinutes(15));
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var serversRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
||||||
|
var nodesRepo = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
|
||||||
|
var usersRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||||
|
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
|
||||||
|
var databaseRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
|
||||||
|
|
||||||
|
var apiHelper = scope.ServiceProvider.GetRequiredService<TelemetryApiHelper>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await apiHelper.Post("telemetry", new TelemetryData()
|
||||||
|
{
|
||||||
|
Servers = serversRepo.Get().Count(),
|
||||||
|
Databases = databaseRepo.Get().Count(),
|
||||||
|
Nodes = nodesRepo.Get().Count(),
|
||||||
|
Users = usersRepo.Get().Count(),
|
||||||
|
Webspaces = webspacesRepo.Get().Count(),
|
||||||
|
AppUrl = ConfigService.Get().Moonlight.AppUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn("Error sending telemetry");
|
||||||
|
Logger.Warn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
await timer.WaitForNextTickAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
using System.Text;
|
using Moonlight.App.Configuration;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Services.Files;
|
using Moonlight.App.Services.Files;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
public class ConfigService : IConfiguration
|
public class ConfigService
|
||||||
{
|
{
|
||||||
private readonly StorageService StorageService;
|
private readonly StorageService StorageService;
|
||||||
|
private ConfigV1 Configuration;
|
||||||
private IConfiguration Configuration;
|
|
||||||
|
|
||||||
public bool DebugMode { get; private set; } = false;
|
public bool DebugMode { get; private set; } = false;
|
||||||
public bool SqlDebugMode { get; private set; } = false;
|
public bool SqlDebugMode { get; private set; } = false;
|
||||||
@@ -41,33 +40,42 @@ public class ConfigService : IConfiguration
|
|||||||
|
|
||||||
public void Reload()
|
public void Reload()
|
||||||
{
|
{
|
||||||
Configuration = new ConfigurationBuilder().AddJsonStream(
|
var path = PathBuilder.File("storage", "configs", "config.json");
|
||||||
new MemoryStream(Encoding.ASCII.GetBytes(
|
|
||||||
File.ReadAllText(
|
if (!File.Exists(path))
|
||||||
PathBuilder.File("storage", "configs", "config.json")
|
{
|
||||||
)
|
File.WriteAllText(path, "{}");
|
||||||
)
|
}
|
||||||
)).Build();
|
|
||||||
|
Configuration = JsonConvert.DeserializeObject<ConfigV1>(
|
||||||
|
File.ReadAllText(path)
|
||||||
|
) ?? new ConfigV1();
|
||||||
|
|
||||||
|
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IConfigurationSection> GetChildren()
|
public void Save(ConfigV1 configV1)
|
||||||
{
|
{
|
||||||
return Configuration.GetChildren();
|
Configuration = configV1;
|
||||||
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IChangeToken GetReloadToken()
|
public void Save()
|
||||||
{
|
{
|
||||||
return Configuration.GetReloadToken();
|
var path = PathBuilder.File("storage", "configs", "config.json");
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
|
|
||||||
|
Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationSection GetSection(string key)
|
public ConfigV1 Get()
|
||||||
{
|
{
|
||||||
return Configuration.GetSection(key);
|
return Configuration;
|
||||||
}
|
|
||||||
|
|
||||||
public string this[string key]
|
|
||||||
{
|
|
||||||
get => Configuration[key];
|
|
||||||
set => Configuration[key] = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ public class ServerListCommand : BaseModule
|
|||||||
{
|
{
|
||||||
embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, command.User);
|
embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, command.User);
|
||||||
components = new ComponentBuilder();
|
components = new ComponentBuilder();
|
||||||
components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.GetSection("Moonlight").GetValue<String>("AppUrl"));
|
components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.Get().Moonlight.AppUrl);
|
||||||
|
|
||||||
await command.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true);
|
await command.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true);
|
||||||
return;
|
return;
|
||||||
@@ -57,7 +57,7 @@ public class ServerListCommand : BaseModule
|
|||||||
components.WithButton("Panel",
|
components.WithButton("Panel",
|
||||||
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
|
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
|
||||||
style: ButtonStyle.Link,
|
style: ButtonStyle.Link,
|
||||||
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}");
|
url: $"{ConfigService.Get().Moonlight.AppUrl}");
|
||||||
|
|
||||||
if (servers.Count > 25)
|
if (servers.Count > 25)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ public DiscordBotService(
|
|||||||
ServiceScope = ServiceScopeFactory.CreateScope();
|
ServiceScope = ServiceScopeFactory.CreateScope();
|
||||||
|
|
||||||
var discordConfig = ConfigService
|
var discordConfig = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("DiscordBot");
|
.Moonlight.DiscordBot;
|
||||||
|
|
||||||
if (!discordConfig.GetValue<bool>("Enable"))
|
if (!discordConfig.Enable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Client.Log += Log;
|
Client.Log += Log;
|
||||||
@@ -67,7 +67,7 @@ public DiscordBotService(
|
|||||||
|
|
||||||
await ActivityStatusModule.UpdateActivityStatusList();
|
await ActivityStatusModule.UpdateActivityStatusList();
|
||||||
|
|
||||||
await Client.LoginAsync(TokenType.Bot, discordConfig.GetValue<string>("Token"));
|
await Client.LoginAsync(TokenType.Bot, discordConfig.Token);
|
||||||
await Client.StartAsync();
|
await Client.StartAsync();
|
||||||
|
|
||||||
await Task.Delay(-1);
|
await Task.Delay(-1);
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ public class EmbedBuilderModule : BaseModule
|
|||||||
int[] randomNumbers = new int[] { 1, 3, 8, 11, 20 };
|
int[] randomNumbers = new int[] { 1, 3, 8, 11, 20 };
|
||||||
|
|
||||||
if (randomNumbers.Contains(random.Next(1, 24)))
|
if (randomNumbers.Contains(random.Next(1, 24)))
|
||||||
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username + " - The Rick version").WithUrl(ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")).WithIconUrl("https://cdn.discordapp.com/attachments/750696464014901268/1092783310129860618/rick.gif");
|
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username + " - The Rick version").WithUrl(ConfigService.Get().Moonlight.AppUrl).WithIconUrl("https://cdn.discordapp.com/attachments/750696464014901268/1092783310129860618/rick.gif");
|
||||||
|
|
||||||
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username).WithUrl(ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")).WithIconUrl(Client.CurrentUser.GetAvatarUrl());
|
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username).WithUrl(ConfigService.Get().Moonlight.AppUrl).WithIconUrl(Client.CurrentUser.GetAvatarUrl());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class ServerListComponentHandlerModule : BaseModule
|
|||||||
// stopping
|
// stopping
|
||||||
// offline
|
// offline
|
||||||
// installing
|
// installing
|
||||||
if (!ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("PowerActions") && costomId[1] is "Start" or "Restart" or "Stop" or "Kill" or "Update")
|
if (!ConfigService.Get().Moonlight.DiscordBot.PowerActions && costomId[1] is "Start" or "Restart" or "Stop" or "Kill" or "Update")
|
||||||
{
|
{
|
||||||
embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User);
|
embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User);
|
||||||
await component.RespondAsync(embed: embed.Build(), ephemeral: true);
|
await component.RespondAsync(embed: embed.Build(), ephemeral: true);
|
||||||
@@ -80,7 +80,7 @@ public class ServerListComponentHandlerModule : BaseModule
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("SendCommands") && costomId[1] is "SendCommand")
|
if (!ConfigService.Get().Moonlight.DiscordBot.SendCommands && costomId[1] is "SendCommand")
|
||||||
{
|
{
|
||||||
embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User);
|
embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User);
|
||||||
await component.RespondAsync(embed: embed.Build(), ephemeral: true);
|
await component.RespondAsync(embed: embed.Build(), ephemeral: true);
|
||||||
@@ -302,7 +302,7 @@ public class ServerListComponentHandlerModule : BaseModule
|
|||||||
{
|
{
|
||||||
embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, component.User);
|
embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, component.User);
|
||||||
components = new ComponentBuilder();
|
components = new ComponentBuilder();
|
||||||
components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.GetSection("Moonlight").GetValue<String>("AppUrl"));
|
components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.Get().Moonlight.AppUrl);
|
||||||
|
|
||||||
await component.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true);
|
await component.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true);
|
||||||
return;
|
return;
|
||||||
@@ -332,7 +332,7 @@ public class ServerListComponentHandlerModule : BaseModule
|
|||||||
components.WithButton("Panel",
|
components.WithButton("Panel",
|
||||||
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
|
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
|
||||||
style: ButtonStyle.Link,
|
style: ButtonStyle.Link,
|
||||||
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}");
|
url: $"{ConfigService.Get().Moonlight.AppUrl}");
|
||||||
|
|
||||||
components.WithButton("Previous-page",
|
components.WithButton("Previous-page",
|
||||||
emote: Emote.Parse("<:ArrowLeft:1101547474180649030>"),
|
emote: Emote.Parse("<:ArrowLeft:1101547474180649030>"),
|
||||||
@@ -378,7 +378,7 @@ public class ServerListComponentHandlerModule : BaseModule
|
|||||||
|
|
||||||
var components = new ComponentBuilder();
|
var components = new ComponentBuilder();
|
||||||
|
|
||||||
if (ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("PowerActions"))
|
if (ConfigService.Get().Moonlight.DiscordBot.PowerActions)
|
||||||
{
|
{
|
||||||
components.WithButton("Start", style: ButtonStyle.Success, customId: $"Sm.Start.{server.Id}", disabled: false);
|
components.WithButton("Start", style: ButtonStyle.Success, customId: $"Sm.Start.{server.Id}", disabled: false);
|
||||||
components.WithButton("Restart", style: ButtonStyle.Primary, customId: $"Sm.Restart.{server.Id}", disabled: false);
|
components.WithButton("Restart", style: ButtonStyle.Primary, customId: $"Sm.Restart.{server.Id}", disabled: false);
|
||||||
@@ -389,14 +389,14 @@ public class ServerListComponentHandlerModule : BaseModule
|
|||||||
components.WithButton("Way2Server",
|
components.WithButton("Way2Server",
|
||||||
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
|
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
|
||||||
style: ButtonStyle.Link,
|
style: ButtonStyle.Link,
|
||||||
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}/server/{server.Uuid}");
|
url: $"{ConfigService.Get().Moonlight.AppUrl}/server/{server.Uuid}");
|
||||||
|
|
||||||
components.WithButton("Update",
|
components.WithButton("Update",
|
||||||
emote: Emote.Parse("<:refresh:1101547898803605605>"),
|
emote: Emote.Parse("<:refresh:1101547898803605605>"),
|
||||||
style: ButtonStyle.Secondary,
|
style: ButtonStyle.Secondary,
|
||||||
customId: $"Sm.Update.{server.Id}");
|
customId: $"Sm.Update.{server.Id}");
|
||||||
|
|
||||||
if (ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("SendCommands"))
|
if (ConfigService.Get().Moonlight.DiscordBot.SendCommands)
|
||||||
{
|
{
|
||||||
components.WithButton("SendCommand",
|
components.WithButton("SendCommand",
|
||||||
emote: Emote.Parse("<:Console:1101547358157819944>"),
|
emote: Emote.Parse("<:Console:1101547358157819944>"),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using CloudFlare.Client;
|
using CloudFlare.Client;
|
||||||
using CloudFlare.Client.Api.Authentication;
|
using CloudFlare.Client.Api.Authentication;
|
||||||
|
using CloudFlare.Client.Api.Display;
|
||||||
using CloudFlare.Client.Api.Parameters.Data;
|
using CloudFlare.Client.Api.Parameters.Data;
|
||||||
using CloudFlare.Client.Api.Result;
|
using CloudFlare.Client.Api.Result;
|
||||||
using CloudFlare.Client.Api.Zones;
|
using CloudFlare.Client.Api.Zones;
|
||||||
@@ -12,12 +13,14 @@ using Moonlight.App.Helpers;
|
|||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories.Domains;
|
using Moonlight.App.Repositories.Domains;
|
||||||
using DnsRecord = Moonlight.App.Models.Misc.DnsRecord;
|
using DnsRecord = Moonlight.App.Models.Misc.DnsRecord;
|
||||||
|
using MatchType = CloudFlare.Client.Enumerators.MatchType;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
public class DomainService
|
public class DomainService
|
||||||
{
|
{
|
||||||
private readonly DomainRepository DomainRepository;
|
private readonly DomainRepository DomainRepository;
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
private readonly SharedDomainRepository SharedDomainRepository;
|
private readonly SharedDomainRepository SharedDomainRepository;
|
||||||
private readonly CloudFlareClient Client;
|
private readonly CloudFlareClient Client;
|
||||||
private readonly string AccountId;
|
private readonly string AccountId;
|
||||||
@@ -27,25 +30,29 @@ public class DomainService
|
|||||||
DomainRepository domainRepository,
|
DomainRepository domainRepository,
|
||||||
SharedDomainRepository sharedDomainRepository)
|
SharedDomainRepository sharedDomainRepository)
|
||||||
{
|
{
|
||||||
|
ConfigService = configService;
|
||||||
DomainRepository = domainRepository;
|
DomainRepository = domainRepository;
|
||||||
SharedDomainRepository = sharedDomainRepository;
|
SharedDomainRepository = sharedDomainRepository;
|
||||||
|
|
||||||
var config = configService
|
var config = configService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Domains");
|
.Moonlight.Domains;
|
||||||
|
|
||||||
AccountId = config.GetValue<string>("AccountId");
|
AccountId = config.AccountId;
|
||||||
|
|
||||||
Client = new(
|
Client = new(
|
||||||
new ApiKeyAuthentication(
|
new ApiKeyAuthentication(
|
||||||
config.GetValue<string>("Email"),
|
config.Email,
|
||||||
config.GetValue<string>("Key")
|
config.Key
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Domain> Create(string domain, SharedDomain sharedDomain, User user)
|
public Task<Domain> Create(string domain, SharedDomain sharedDomain, User user)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
if (DomainRepository.Get().Where(x => x.SharedDomain.Id == sharedDomain.Id).Any(x => x.Name == domain))
|
if (DomainRepository.Get().Where(x => x.SharedDomain.Id == sharedDomain.Id).Any(x => x.Name == domain))
|
||||||
throw new DisplayException("A domain with this name does already exist for this shared domain");
|
throw new DisplayException("A domain with this name does already exist for this shared domain");
|
||||||
|
|
||||||
@@ -61,6 +68,9 @@ public class DomainService
|
|||||||
|
|
||||||
public Task Delete(Domain domain)
|
public Task Delete(Domain domain)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
DomainRepository.Delete(domain);
|
DomainRepository.Delete(domain);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -69,6 +79,9 @@ public class DomainService
|
|||||||
public async Task<Zone[]>
|
public async Task<Zone[]>
|
||||||
GetAvailableDomains() // This method returns all available domains which are not added as a shared domain
|
GetAvailableDomains() // This method returns all available domains which are not added as a shared domain
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
return Array.Empty<Zone>();
|
||||||
|
|
||||||
var domains = GetData(
|
var domains = GetData(
|
||||||
await Client.Zones.GetAsync(new()
|
await Client.Zones.GetAsync(new()
|
||||||
{
|
{
|
||||||
@@ -91,11 +104,41 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task<DnsRecord[]> GetDnsRecords(Domain d)
|
public async Task<DnsRecord[]> GetDnsRecords(Domain d)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
return Array.Empty<DnsRecord>();
|
||||||
|
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
var records = GetData(
|
var records = new List<CloudFlare.Client.Api.Zones.DnsRecord.DnsRecord>();
|
||||||
await Client.Zones.DnsRecords.GetAsync(domain.SharedDomain.CloudflareId)
|
|
||||||
);
|
// Load paginated
|
||||||
|
// TODO: Find an alternative option. This way to load the records is NOT optimal
|
||||||
|
// and can result in long loading time when there are many dns records.
|
||||||
|
// As cloudflare does not offer a way to search dns records which starts
|
||||||
|
// with a specific string we are not able to filter it using the api (client)
|
||||||
|
var initialResponse = await Client.Zones.DnsRecords.GetAsync(domain.SharedDomain.CloudflareId);
|
||||||
|
|
||||||
|
records.AddRange(GetData(initialResponse));
|
||||||
|
|
||||||
|
// Check if there are more pages
|
||||||
|
while (initialResponse.ResultInfo.Page < initialResponse.ResultInfo.TotalPage)
|
||||||
|
{
|
||||||
|
// Get the next page of data
|
||||||
|
var nextPageResponse = await Client.Zones.DnsRecords.GetAsync(
|
||||||
|
domain.SharedDomain.CloudflareId,
|
||||||
|
displayOptions: new()
|
||||||
|
{
|
||||||
|
Page = initialResponse.ResultInfo.Page + 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var nextPageRecords = GetData(nextPageResponse);
|
||||||
|
|
||||||
|
// Append the records from the next page to the existing records
|
||||||
|
records.AddRange(nextPageRecords);
|
||||||
|
|
||||||
|
// Update the initial response to the next page response
|
||||||
|
initialResponse = nextPageResponse;
|
||||||
|
}
|
||||||
|
|
||||||
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
||||||
var dname = $".{rname}";
|
var dname = $".{rname}";
|
||||||
@@ -117,7 +160,7 @@ public class DomainService
|
|||||||
Type = record.Type
|
Type = record.Type
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (record.Name.EndsWith(rname))
|
else if (record.Name == rname)
|
||||||
{
|
{
|
||||||
result.Add(new()
|
result.Add(new()
|
||||||
{
|
{
|
||||||
@@ -137,6 +180,9 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
||||||
@@ -145,7 +191,11 @@ public class DomainService
|
|||||||
if (dnsRecord.Type == DnsRecordType.Srv)
|
if (dnsRecord.Type == DnsRecordType.Srv)
|
||||||
{
|
{
|
||||||
var parts = dnsRecord.Name.Split(".");
|
var parts = dnsRecord.Name.Split(".");
|
||||||
Enum.TryParse(parts[1], out Protocol protocol);
|
|
||||||
|
Protocol protocol = Protocol.Tcp;
|
||||||
|
|
||||||
|
if (parts[1].Contains("udp"))
|
||||||
|
protocol = Protocol.Udp;
|
||||||
|
|
||||||
var valueParts = dnsRecord.Content.Split(" ");
|
var valueParts = dnsRecord.Content.Split(" ");
|
||||||
|
|
||||||
@@ -192,6 +242,9 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
||||||
@@ -222,6 +275,9 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
GetData(
|
GetData(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class ResourceService
|
|||||||
|
|
||||||
public ResourceService(ConfigService configService)
|
public ResourceService(ConfigService configService)
|
||||||
{
|
{
|
||||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
AppUrl = configService.Get().Moonlight.AppUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Image(string name)
|
public string Image(string name)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class StorageService
|
|||||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "configs"));
|
Directory.CreateDirectory(PathBuilder.Dir("storage", "configs"));
|
||||||
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"));
|
||||||
|
|
||||||
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
|
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,21 +1,40 @@
|
|||||||
using CurrieTechnologies.Razor.SweetAlert2;
|
using CurrieTechnologies.Razor.SweetAlert2;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Interop;
|
namespace Moonlight.App.Services.Interop;
|
||||||
|
|
||||||
public class AlertService
|
public class AlertService
|
||||||
{
|
{
|
||||||
private readonly SweetAlertService SweetAlertService;
|
|
||||||
private readonly SmartTranslateService SmartTranslateService;
|
private readonly SmartTranslateService SmartTranslateService;
|
||||||
|
private readonly IJSRuntime JsRuntime;
|
||||||
|
private SweetAlertService? SweetAlertService;
|
||||||
|
|
||||||
public AlertService(SweetAlertService service, SmartTranslateService smartTranslateService)
|
public AlertService(SmartTranslateService smartTranslateService, IJSRuntime jsRuntime)
|
||||||
{
|
{
|
||||||
SweetAlertService = service;
|
|
||||||
SmartTranslateService = smartTranslateService;
|
SmartTranslateService = smartTranslateService;
|
||||||
|
JsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create the swal service here and not using the dependency injection
|
||||||
|
// because it initializes when instantiated which leads to js invoke errors
|
||||||
|
private Task EnsureService()
|
||||||
|
{
|
||||||
|
if (SweetAlertService == null)
|
||||||
|
{
|
||||||
|
SweetAlertService = new(JsRuntime, new()
|
||||||
|
{
|
||||||
|
Theme = SweetAlertTheme.Dark
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Info(string title, string desciption)
|
public async Task Info(string title, string desciption)
|
||||||
{
|
{
|
||||||
await SweetAlertService.FireAsync(new SweetAlertOptions()
|
await EnsureService();
|
||||||
|
|
||||||
|
await SweetAlertService!.FireAsync(new SweetAlertOptions()
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Text = desciption,
|
Text = desciption,
|
||||||
@@ -30,7 +49,9 @@ public class AlertService
|
|||||||
|
|
||||||
public async Task Success(string title, string desciption)
|
public async Task Success(string title, string desciption)
|
||||||
{
|
{
|
||||||
await SweetAlertService.FireAsync(new SweetAlertOptions()
|
await EnsureService();
|
||||||
|
|
||||||
|
await SweetAlertService!.FireAsync(new SweetAlertOptions()
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Text = desciption,
|
Text = desciption,
|
||||||
@@ -45,7 +66,9 @@ public class AlertService
|
|||||||
|
|
||||||
public async Task Warning(string title, string desciption)
|
public async Task Warning(string title, string desciption)
|
||||||
{
|
{
|
||||||
await SweetAlertService.FireAsync(new SweetAlertOptions()
|
await EnsureService();
|
||||||
|
|
||||||
|
await SweetAlertService!.FireAsync(new SweetAlertOptions()
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Text = desciption,
|
Text = desciption,
|
||||||
@@ -60,7 +83,9 @@ public class AlertService
|
|||||||
|
|
||||||
public async Task Error(string title, string desciption)
|
public async Task Error(string title, string desciption)
|
||||||
{
|
{
|
||||||
await SweetAlertService.FireAsync(new SweetAlertOptions()
|
await EnsureService();
|
||||||
|
|
||||||
|
await SweetAlertService!.FireAsync(new SweetAlertOptions()
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Text = desciption,
|
Text = desciption,
|
||||||
@@ -75,7 +100,9 @@ public class AlertService
|
|||||||
|
|
||||||
public async Task<bool> YesNo(string title, string desciption, string yesText, string noText)
|
public async Task<bool> YesNo(string title, string desciption, string yesText, string noText)
|
||||||
{
|
{
|
||||||
var result = await SweetAlertService.FireAsync(new SweetAlertOptions()
|
await EnsureService();
|
||||||
|
|
||||||
|
var result = await SweetAlertService!.FireAsync(new SweetAlertOptions()
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Text = desciption,
|
Text = desciption,
|
||||||
@@ -91,7 +118,9 @@ public class AlertService
|
|||||||
|
|
||||||
public async Task<string?> Text(string title, string desciption, string setValue)
|
public async Task<string?> Text(string title, string desciption, string setValue)
|
||||||
{
|
{
|
||||||
var result = await SweetAlertService.FireAsync(new SweetAlertOptions()
|
await EnsureService();
|
||||||
|
|
||||||
|
var result = await SweetAlertService!.FireAsync(new SweetAlertOptions()
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Text = desciption,
|
Text = desciption,
|
||||||
|
|||||||
@@ -25,16 +25,15 @@ public class ReCaptchaService
|
|||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
|
|
||||||
var recaptchaConfig = ConfigService
|
var recaptchaConfig = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Security")
|
.Moonlight.Security.ReCaptcha;
|
||||||
.GetSection("ReCaptcha");
|
|
||||||
|
|
||||||
Enable = recaptchaConfig.GetValue<bool>("Enable");
|
Enable = recaptchaConfig.Enable;
|
||||||
|
|
||||||
if (Enable)
|
if (Enable)
|
||||||
{
|
{
|
||||||
SiteKey = recaptchaConfig.GetValue<string>("SiteKey");
|
SiteKey = recaptchaConfig.SiteKey;
|
||||||
SecretKey = recaptchaConfig.GetValue<string>("SecretKey");
|
SecretKey = recaptchaConfig.SecretKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ public class MailService
|
|||||||
public MailService(ConfigService configService)
|
public MailService(ConfigService configService)
|
||||||
{
|
{
|
||||||
var mailConfig = configService
|
var mailConfig = configService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Mail");
|
.Moonlight.Mail;
|
||||||
|
|
||||||
Server = mailConfig.GetValue<string>("Server");
|
Server = mailConfig.Server;
|
||||||
Password = mailConfig.GetValue<string>("Password");
|
Password = mailConfig.Password;
|
||||||
Email = mailConfig.GetValue<string>("Email");
|
Email = mailConfig.Email;
|
||||||
Port = mailConfig.GetValue<int>("Port");
|
Port = mailConfig.Port;
|
||||||
Ssl = mailConfig.GetValue<bool>("Ssl");
|
Ssl = mailConfig.Ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendMail(
|
public async Task SendMail(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
using Mappy.Net;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
@@ -24,14 +25,17 @@ public class OAuth2Service
|
|||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
ServiceScopeFactory = serviceScopeFactory;
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
|
||||||
var config = ConfigService.GetSection("Moonlight").GetSection("OAuth2");
|
var config = ConfigService
|
||||||
|
.Get()
|
||||||
|
.Moonlight.OAuth2;
|
||||||
|
|
||||||
Configs = config.GetSection("Providers").Get<OAuth2ProviderConfig[]>()
|
Configs = config.Providers
|
||||||
?? Array.Empty<OAuth2ProviderConfig>();
|
.Select(Mapper.Map<OAuth2ProviderConfig>)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
OverrideUrl = config.GetValue<string>("OverrideUrl");
|
OverrideUrl = config.OverrideUrl;
|
||||||
EnableOverrideUrl = config.GetValue<bool>("EnableOverrideUrl");
|
EnableOverrideUrl = config.EnableOverrideUrl;
|
||||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
AppUrl = configService.Get().Moonlight.AppUrl;
|
||||||
|
|
||||||
// Register additional providers here
|
// Register additional providers here
|
||||||
RegisterOAuth2<DiscordOAuth2Provider>("discord");
|
RegisterOAuth2<DiscordOAuth2Provider>("discord");
|
||||||
|
|||||||
@@ -25,10 +25,9 @@ public class OneTimeJwtService
|
|||||||
var opt = new Dictionary<string, string>();
|
var opt = new Dictionary<string, string>();
|
||||||
options.Invoke(opt);
|
options.Invoke(opt);
|
||||||
|
|
||||||
string secret = ConfigService
|
var secret = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Security")
|
.Moonlight.Security.Token;
|
||||||
.GetValue<string>("Token");
|
|
||||||
|
|
||||||
var id = StringHelper.GenerateString(16);
|
var id = StringHelper.GenerateString(16);
|
||||||
|
|
||||||
@@ -55,10 +54,9 @@ public class OneTimeJwtService
|
|||||||
|
|
||||||
public async Task<Dictionary<string, string>?> Validate(string token)
|
public async Task<Dictionary<string, string>?> Validate(string token)
|
||||||
{
|
{
|
||||||
string secret = ConfigService
|
var secret = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Security")
|
.Moonlight.Security.Token;
|
||||||
.GetValue<string>("Token");
|
|
||||||
|
|
||||||
string json;
|
string json;
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ public class RatingService
|
|||||||
Event = eventSystem;
|
Event = eventSystem;
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
|
|
||||||
var config = configService.GetSection("Moonlight").GetSection("Rating");
|
var config = configService.Get().Moonlight.Rating;
|
||||||
|
|
||||||
Enabled = config.GetValue<bool>("Enabled");
|
Enabled = config.Enabled;
|
||||||
Url = config.GetValue<string>("Url");
|
Url = config.Url;
|
||||||
MinRating = config.GetValue<int>("MinRating");
|
MinRating = config.MinRating;
|
||||||
DaysSince = config.GetValue<int>("DaysSince");
|
DaysSince = config.DaysSince;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ShouldRate()
|
public async Task<bool> ShouldRate()
|
||||||
|
|||||||
@@ -79,10 +79,20 @@ public class ServerService
|
|||||||
{
|
{
|
||||||
Server server = EnsureNodeData(s);
|
Server server = EnsureNodeData(s);
|
||||||
|
|
||||||
return await WingsApiHelper.Get<ServerDetails>(
|
ServerDetails result = null!;
|
||||||
server.Node,
|
|
||||||
$"api/servers/{server.Uuid}"
|
await new Retry()
|
||||||
);
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
|
{
|
||||||
|
result = await WingsApiHelper.Get<ServerDetails>(
|
||||||
|
server.Node,
|
||||||
|
$"api/servers/{server.Uuid}"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetPowerState(Server s, PowerSignal signal)
|
public async Task SetPowerState(Server s, PowerSignal signal)
|
||||||
@@ -109,7 +119,8 @@ public class ServerService
|
|||||||
|
|
||||||
var backup = new ServerBackup()
|
var backup = new ServerBackup()
|
||||||
{
|
{
|
||||||
Name = $"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
|
Name =
|
||||||
|
$"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
|
||||||
Uuid = Guid.NewGuid(),
|
Uuid = Guid.NewGuid(),
|
||||||
CreatedAt = DateTimeService.GetCurrent(),
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
Created = false
|
Created = false
|
||||||
@@ -175,8 +186,15 @@ public class ServerService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
|
await new Retry()
|
||||||
null);
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
|
{
|
||||||
|
await WingsApiHelper.Delete(serverData.Node,
|
||||||
|
$"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
|
||||||
|
null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (WingsException e)
|
catch (WingsException e)
|
||||||
{
|
{
|
||||||
@@ -257,7 +275,8 @@ public class ServerService
|
|||||||
|
|
||||||
freeAllocations = NodeAllocationRepository
|
freeAllocations = NodeAllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
|
.FromSqlRaw(
|
||||||
|
$"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -285,7 +304,8 @@ public class ServerService
|
|||||||
Allocations = freeAllocations.ToList(),
|
Allocations = freeAllocations.ToList(),
|
||||||
Backups = new(),
|
Backups = new(),
|
||||||
OverrideStartup = "",
|
OverrideStartup = "",
|
||||||
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default)
|
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default),
|
||||||
|
Installing = true
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var imageVariable in image.Variables)
|
foreach (var imageVariable in image.Variables)
|
||||||
@@ -304,11 +324,17 @@ public class ServerService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
await new Retry()
|
||||||
{
|
.Times(3)
|
||||||
Uuid = newServerData.Uuid,
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
StartOnCompletion = false
|
.Call(async () =>
|
||||||
});
|
{
|
||||||
|
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
||||||
|
{
|
||||||
|
Uuid = newServerData.Uuid,
|
||||||
|
StartOnCompletion = false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//TODO: AuditLog
|
//TODO: AuditLog
|
||||||
|
|
||||||
@@ -369,8 +395,6 @@ public class ServerService
|
|||||||
|
|
||||||
public async Task Delete(Server s)
|
public async Task Delete(Server s)
|
||||||
{
|
{
|
||||||
throw new DisplayException("Deleting servers is currently disabled");
|
|
||||||
|
|
||||||
var backups = await GetBackups(s);
|
var backups = await GetBackups(s);
|
||||||
|
|
||||||
foreach (var backup in backups)
|
foreach (var backup in backups)
|
||||||
@@ -391,7 +415,21 @@ public class ServerService
|
|||||||
.Include(x => x.Node)
|
.Include(x => x.Node)
|
||||||
.First(x => x.Id == s.Id);
|
.First(x => x.Id == s.Id);
|
||||||
|
|
||||||
await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null);
|
try
|
||||||
|
{
|
||||||
|
await new Retry()
|
||||||
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
|
{
|
||||||
|
await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (WingsException e)
|
||||||
|
{
|
||||||
|
if (e.StatusCode != 404)
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var variable in server.Variables.ToArray())
|
foreach (var variable in server.Variables.ToArray())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ public class IdentityService
|
|||||||
HttpContextAccessor = httpContextAccessor;
|
HttpContextAccessor = httpContextAccessor;
|
||||||
|
|
||||||
Secret = configService
|
Secret = configService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Security")
|
.Moonlight.Security.Token;
|
||||||
.GetValue<string>("Token");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User?> Get()
|
public async Task<User?> Get()
|
||||||
@@ -120,6 +119,10 @@ public class IdentityService
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
UserCache = user;
|
UserCache = user;
|
||||||
|
|
||||||
|
user.LastIp = GetIp();
|
||||||
|
UserRepository.Update(user);
|
||||||
|
|
||||||
return UserCache;
|
return UserCache;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
60
Moonlight/App/Services/Sessions/SessionClientService.cs
Normal file
60
Moonlight/App/Services/Sessions/SessionClientService.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
using Moonlight.App.Services.Interop;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
|
public class SessionClientService
|
||||||
|
{
|
||||||
|
public readonly Guid Uuid = Guid.NewGuid();
|
||||||
|
public readonly DateTime CreateTimestamp = DateTime.UtcNow;
|
||||||
|
public User? User { get; private set; }
|
||||||
|
public string Ip { get; private set; } = "N/A";
|
||||||
|
public string Device { get; private set; } = "N/A";
|
||||||
|
|
||||||
|
public readonly IdentityService IdentityService;
|
||||||
|
public readonly AlertService AlertService;
|
||||||
|
public readonly NavigationManager NavigationManager;
|
||||||
|
public readonly IJSRuntime JsRuntime;
|
||||||
|
|
||||||
|
private readonly SessionServerService SessionServerService;
|
||||||
|
private readonly Repository<User> UserRepository;
|
||||||
|
|
||||||
|
public SessionClientService(
|
||||||
|
IdentityService identityService,
|
||||||
|
AlertService alertService,
|
||||||
|
NavigationManager navigationManager,
|
||||||
|
IJSRuntime jsRuntime,
|
||||||
|
SessionServerService sessionServerService,
|
||||||
|
Repository<User> userRepository)
|
||||||
|
{
|
||||||
|
IdentityService = identityService;
|
||||||
|
AlertService = alertService;
|
||||||
|
NavigationManager = navigationManager;
|
||||||
|
JsRuntime = jsRuntime;
|
||||||
|
SessionServerService = sessionServerService;
|
||||||
|
UserRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start()
|
||||||
|
{
|
||||||
|
User = await IdentityService.Get();
|
||||||
|
Ip = IdentityService.GetIp();
|
||||||
|
Device = IdentityService.GetDevice();
|
||||||
|
|
||||||
|
if (User != null) // Track users last visit
|
||||||
|
{
|
||||||
|
User.LastVisitedAt = DateTime.UtcNow;
|
||||||
|
UserRepository.Update(User);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SessionServerService.Register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Stop()
|
||||||
|
{
|
||||||
|
await SessionServerService.UnRegister(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Moonlight/App/Services/Sessions/SessionServerService.cs
Normal file
64
Moonlight/App/Services/Sessions/SessionServerService.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
|
public class SessionServerService
|
||||||
|
{
|
||||||
|
private readonly List<SessionClientService> Sessions = new();
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
|
||||||
|
public SessionServerService(EventSystem eventSystem)
|
||||||
|
{
|
||||||
|
Event = eventSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Register(SessionClientService sessionClientService)
|
||||||
|
{
|
||||||
|
lock (Sessions)
|
||||||
|
{
|
||||||
|
if(!Sessions.Contains(sessionClientService))
|
||||||
|
Sessions.Add(sessionClientService);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Event.Emit("sessions.add", sessionClientService);
|
||||||
|
}
|
||||||
|
public async Task UnRegister(SessionClientService sessionClientService)
|
||||||
|
{
|
||||||
|
lock (Sessions)
|
||||||
|
{
|
||||||
|
if(Sessions.Contains(sessionClientService))
|
||||||
|
Sessions.Remove(sessionClientService);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Event.Emit("sessions.remove", sessionClientService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<SessionClientService[]> GetSessions()
|
||||||
|
{
|
||||||
|
lock (Sessions)
|
||||||
|
{
|
||||||
|
return Task.FromResult(Sessions.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReloadUserSessions(User user)
|
||||||
|
{
|
||||||
|
var sessions = await GetSessions();
|
||||||
|
|
||||||
|
foreach (var session in sessions)
|
||||||
|
{
|
||||||
|
if (session.User != null && session.User.Id == user.Id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
session.NavigationManager.NavigateTo(session.NavigationManager.Uri, true);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Repositories;
|
|
||||||
using Moonlight.App.Services.Interop;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Sessions;
|
|
||||||
|
|
||||||
public class SessionService
|
|
||||||
{
|
|
||||||
private readonly SessionRepository SessionRepository;
|
|
||||||
private Repository<User> UserRepository;
|
|
||||||
private readonly IdentityService IdentityService;
|
|
||||||
private readonly NavigationManager NavigationManager;
|
|
||||||
private readonly AlertService AlertService;
|
|
||||||
private readonly DateTimeService DateTimeService;
|
|
||||||
|
|
||||||
private Session? OwnSession;
|
|
||||||
|
|
||||||
public SessionService(
|
|
||||||
SessionRepository sessionRepository,
|
|
||||||
IdentityService identityService,
|
|
||||||
NavigationManager navigationManager,
|
|
||||||
AlertService alertService,
|
|
||||||
DateTimeService dateTimeService,
|
|
||||||
Repository<User> userRepository)
|
|
||||||
{
|
|
||||||
SessionRepository = sessionRepository;
|
|
||||||
IdentityService = identityService;
|
|
||||||
NavigationManager = navigationManager;
|
|
||||||
AlertService = alertService;
|
|
||||||
DateTimeService = dateTimeService;
|
|
||||||
UserRepository = userRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Register()
|
|
||||||
{
|
|
||||||
var user = await IdentityService.Get();
|
|
||||||
|
|
||||||
OwnSession = new Session()
|
|
||||||
{
|
|
||||||
Ip = IdentityService.GetIp(),
|
|
||||||
Url = NavigationManager.Uri,
|
|
||||||
Device = IdentityService.GetDevice(),
|
|
||||||
CreatedAt = DateTimeService.GetCurrent(),
|
|
||||||
User = user,
|
|
||||||
Navigation = NavigationManager,
|
|
||||||
AlertService = AlertService
|
|
||||||
};
|
|
||||||
|
|
||||||
SessionRepository.Add(OwnSession);
|
|
||||||
|
|
||||||
if (user != null) // Track last session init of user as last visited timestamp
|
|
||||||
{
|
|
||||||
user.LastVisitedAt = DateTimeService.GetCurrent();
|
|
||||||
UserRepository.Update(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Refresh()
|
|
||||||
{
|
|
||||||
OwnSession.Url = NavigationManager.Uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
SessionRepository.Delete(OwnSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Session[] GetAll()
|
|
||||||
{
|
|
||||||
return SessionRepository.Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadUserSessions(User user)
|
|
||||||
{
|
|
||||||
foreach (var session in SessionRepository.Get())
|
|
||||||
{
|
|
||||||
if(session.User != null && session.User.Id == user.Id)
|
|
||||||
session.Navigation.NavigateTo(session.Navigation.Uri, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,13 +28,12 @@ public class SmartDeployService
|
|||||||
public async Task<Node?> GetNode()
|
public async Task<Node?> GetNode()
|
||||||
{
|
{
|
||||||
var config = ConfigService
|
var config = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("SmartDeploy")
|
.Moonlight.SmartDeploy.Server;
|
||||||
.GetSection("Server");
|
|
||||||
|
|
||||||
if (config.GetValue<bool>("EnableOverride"))
|
if (config.EnableOverride)
|
||||||
{
|
{
|
||||||
var nodeId = config.GetValue<int>("OverrideNode");
|
var nodeId = config.OverrideNode;
|
||||||
|
|
||||||
return NodeRepository.Get().FirstOrDefault(x => x.Id == nodeId);
|
return NodeRepository.Get().FirstOrDefault(x => x.Id == nodeId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ public class StatisticsCaptureService
|
|||||||
DateTimeService = dateTimeService;
|
DateTimeService = dateTimeService;
|
||||||
|
|
||||||
var config = configService
|
var config = configService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Statistics");
|
.Moonlight.Statistics;
|
||||||
|
|
||||||
if(!config.GetValue<bool>("Enabled"))
|
if(!config.Enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var period = TimeSpan.FromMinutes(config.GetValue<int>("Wait"));
|
var period = TimeSpan.FromMinutes(config.Wait);
|
||||||
Timer = new(period);
|
Timer = new(period);
|
||||||
|
|
||||||
Logger.Info("Starting statistics system");
|
Logger.Info("Starting statistics system");
|
||||||
@@ -36,8 +36,6 @@ public class StatisticsCaptureService
|
|||||||
{
|
{
|
||||||
while (await Timer.WaitForNextTickAsync())
|
while (await Timer.WaitForNextTickAsync())
|
||||||
{
|
{
|
||||||
Logger.Warn("Creating statistics");
|
|
||||||
|
|
||||||
using var scope = ServiceScopeFactory.CreateScope();
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
|
||||||
var statisticsRepo = scope.ServiceProvider.GetRequiredService<Repository<StatisticsData>>();
|
var statisticsRepo = scope.ServiceProvider.GetRequiredService<Repository<StatisticsData>>();
|
||||||
@@ -46,7 +44,7 @@ public class StatisticsCaptureService
|
|||||||
var domainsRepo = scope.ServiceProvider.GetRequiredService<Repository<Domain>>();
|
var domainsRepo = scope.ServiceProvider.GetRequiredService<Repository<Domain>>();
|
||||||
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
|
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
|
||||||
var databasesRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
|
var databasesRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
|
||||||
var sessionService = scope.ServiceProvider.GetRequiredService<SessionService>();
|
var sessionService = scope.ServiceProvider.GetRequiredService<SessionServerService>();
|
||||||
|
|
||||||
void AddEntry(string chart, int value)
|
void AddEntry(string chart, int value)
|
||||||
{
|
{
|
||||||
@@ -63,7 +61,7 @@ public class StatisticsCaptureService
|
|||||||
AddEntry("domainsCount", domainsRepo.Get().Count());
|
AddEntry("domainsCount", domainsRepo.Get().Count());
|
||||||
AddEntry("webspacesCount", webspacesRepo.Get().Count());
|
AddEntry("webspacesCount", webspacesRepo.Get().Count());
|
||||||
AddEntry("databasesCount", databasesRepo.Get().Count());
|
AddEntry("databasesCount", databasesRepo.Get().Count());
|
||||||
AddEntry("sessionsCount", sessionService.GetAll().Length);
|
AddEntry("sessionsCount", (await sessionService.GetSessions()).Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using OtpNet;
|
using OtpNet;
|
||||||
|
|
||||||
@@ -38,21 +39,24 @@ public class TotpService
|
|||||||
return user!.TotpSecret;
|
return user!.TotpSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Enable()
|
public async Task GenerateSecret()
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
user.TotpSecret = GenerateSecret();
|
user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));;
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
//TODO: AuditLog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnforceTotpLogin()
|
public async Task Enable(string code)
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
|
if (!await Verify(user.TotpSecret, code))
|
||||||
|
{
|
||||||
|
throw new DisplayException("The 2fa code you entered is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
user.TotpEnabled = true;
|
user.TotpEnabled = true;
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
}
|
}
|
||||||
@@ -62,14 +66,10 @@ public class TotpService
|
|||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
user.TotpEnabled = false;
|
user.TotpEnabled = false;
|
||||||
|
user.TotpSecret = "";
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
//TODO: AuditLog
|
//TODO: AuditLog
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateSecret()
|
|
||||||
{
|
|
||||||
return Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ public class UserService
|
|||||||
private readonly IdentityService IdentityService;
|
private readonly IdentityService IdentityService;
|
||||||
private readonly IpLocateService IpLocateService;
|
private readonly IpLocateService IpLocateService;
|
||||||
private readonly DateTimeService DateTimeService;
|
private readonly DateTimeService DateTimeService;
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
|
|
||||||
private readonly string JwtSecret;
|
private readonly string JwtSecret;
|
||||||
|
|
||||||
@@ -32,19 +33,22 @@ public class UserService
|
|||||||
{
|
{
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
TotpService = totpService;
|
TotpService = totpService;
|
||||||
|
ConfigService = configService;
|
||||||
MailService = mailService;
|
MailService = mailService;
|
||||||
IdentityService = identityService;
|
IdentityService = identityService;
|
||||||
IpLocateService = ipLocateService;
|
IpLocateService = ipLocateService;
|
||||||
DateTimeService = dateTimeService;
|
DateTimeService = dateTimeService;
|
||||||
|
|
||||||
JwtSecret = configService
|
JwtSecret = configService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Security")
|
.Moonlight.Security.Token;
|
||||||
.GetValue<string>("Token");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Register(string email, string password, string firstname, string lastname)
|
public async Task<string> Register(string email, string password, string firstname, string lastname)
|
||||||
{
|
{
|
||||||
|
if (ConfigService.Get().Moonlight.Auth.DenyRegister)
|
||||||
|
throw new DisplayException("This operation was disabled");
|
||||||
|
|
||||||
// Check if the email is already taken
|
// Check if the email is already taken
|
||||||
var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null;
|
var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null;
|
||||||
|
|
||||||
@@ -73,7 +77,9 @@ public class UserService
|
|||||||
TotpEnabled = false,
|
TotpEnabled = false,
|
||||||
TotpSecret = "",
|
TotpSecret = "",
|
||||||
UpdatedAt = DateTimeService.GetCurrent(),
|
UpdatedAt = DateTimeService.GetCurrent(),
|
||||||
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5)
|
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5),
|
||||||
|
LastIp = IdentityService.GetIp(),
|
||||||
|
RegisterIp = IdentityService.GetIp()
|
||||||
});
|
});
|
||||||
|
|
||||||
await MailService.SendMail(user!, "register", values => {});
|
await MailService.SendMail(user!, "register", values => {});
|
||||||
@@ -107,6 +113,9 @@ public class UserService
|
|||||||
|
|
||||||
public async Task<string> Login(string email, string password, string totpCode = "")
|
public async Task<string> Login(string email, string password, string totpCode = "")
|
||||||
{
|
{
|
||||||
|
if (ConfigService.Get().Moonlight.Auth.DenyLogin)
|
||||||
|
throw new DisplayException("This operation was disabled");
|
||||||
|
|
||||||
// First password check and check if totp is enabled
|
// First password check and check if totp is enabled
|
||||||
var needTotp = await CheckTotp(email, password);
|
var needTotp = await CheckTotp(email, password);
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PackageReference Include="BlazorMonaco" Version="2.1.0" />
|
<PackageReference Include="BlazorMonaco" Version="2.1.0" />
|
||||||
<PackageReference Include="BlazorTable" Version="1.17.0" />
|
<PackageReference Include="BlazorTable" Version="1.17.0" />
|
||||||
<PackageReference Include="CloudFlare.Client" Version="6.1.4" />
|
<PackageReference Include="CloudFlare.Client" Version="6.1.4" />
|
||||||
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
|
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.5.0" />
|
||||||
<PackageReference Include="Discord.Net" Version="3.10.0" />
|
<PackageReference Include="Discord.Net" Version="3.10.0" />
|
||||||
<PackageReference Include="Discord.Net.Webhook" Version="3.10.0" />
|
<PackageReference Include="Discord.Net.Webhook" Version="3.10.0" />
|
||||||
<PackageReference Include="DnsClient" Version="1.7.0" />
|
<PackageReference Include="DnsClient" Version="1.7.0" />
|
||||||
@@ -46,7 +46,10 @@
|
|||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||||
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
|
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
|
||||||
|
<PackageReference Include="Sentry.AspNetCore" Version="3.33.1" />
|
||||||
|
<PackageReference Include="Sentry.Serilog" Version="3.33.1" />
|
||||||
<PackageReference Include="Serilog" Version="3.0.0" />
|
<PackageReference Include="Serilog" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.1-dev-00910" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.1-dev-00910" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00947" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00947" />
|
||||||
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
||||||
@@ -100,4 +103,12 @@
|
|||||||
<AdditionalFiles Include="Shared\Views\Server\Settings\ServerResetSetting.razor" />
|
<AdditionalFiles Include="Shared\Views\Server\Settings\ServerResetSetting.razor" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="storage\configs\config.json.bak">
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
|
|
||||||
@{
|
@{
|
||||||
var headerConfig = ConfigService
|
var headerConfig = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Html")
|
.Moonlight.Html.Headers;
|
||||||
.GetSection("Headers");
|
|
||||||
|
|
||||||
var moonlightConfig = ConfigService
|
var moonlightConfig = ConfigService
|
||||||
.GetSection("Moonlight");
|
.Get()
|
||||||
|
.Moonlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -26,16 +26,16 @@
|
|||||||
|
|
||||||
<meta property="og:locale" content="de_DE"/>
|
<meta property="og:locale" content="de_DE"/>
|
||||||
<meta property="og:type" content="article"/>
|
<meta property="og:type" content="article"/>
|
||||||
<meta content="@(headerConfig.GetValue<string>("Title"))" property="og:title"/>
|
<meta content="@(headerConfig.Title)" property="og:title"/>
|
||||||
<meta content="@(headerConfig.GetValue<string>("Description"))" property="og:description"/>
|
<meta content="@(headerConfig.Description)" property="og:description"/>
|
||||||
<meta content="@(moonlightConfig.GetValue<string>("AppUrl"))" property="og:url"/>
|
<meta content="@(moonlightConfig.AppUrl)" property="og:url"/>
|
||||||
<meta content="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logolong.png" property="og:image"/>
|
<meta content="@(moonlightConfig.AppUrl)/api/moonlight/resources/images/logolong.png" property="og:image"/>
|
||||||
<meta content="@(headerConfig.GetValue<string>("Color"))" data-react-helmet="true" name="theme-color"/>
|
<meta content="@(headerConfig.Color)" data-react-helmet="true" name="theme-color"/>
|
||||||
|
|
||||||
<meta content="@(headerConfig.GetValue<string>("Description"))" name="description"/>
|
<meta content="@(headerConfig.Description)" name="description"/>
|
||||||
<meta content="@(headerConfig.GetValue<string>("Keywords"))" name="keywords"/>
|
<meta content="@(headerConfig.Keywords)" name="keywords"/>
|
||||||
|
|
||||||
<link rel="shortcut icon" href="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg"/>
|
<link rel="shortcut icon" href="@(moonlightConfig.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" href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700"/>
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<div id="flashbang" class="flashbanglight"></div>
|
<div id="flashbang" class="flashbanglight"></div>
|
||||||
|
|
||||||
<div class="app-page-loader flex-column">
|
<div class="app-page-loader flex-column">
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-25px"/>
|
<img alt="Logo" src="@(moonlightConfig.AppUrl)/api/moonlight/resources/images/logo.svg" class="h-25px"/>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
string loadingMessage;
|
string loadingMessage;
|
||||||
@@ -96,13 +96,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/_framework/blazor.server.js"></script>
|
|
||||||
<script src="/assets/plugins/global/plugins.bundle.js"></script>
|
<script src="/assets/plugins/global/plugins.bundle.js"></script>
|
||||||
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
|
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
|
||||||
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
||||||
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
|
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
|
||||||
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
|
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@@shopify/draggable@1.0.0-beta.11/lib/draggable.bundle.js"></script>
|
||||||
|
|
||||||
<script src="https://www.google.com/recaptcha/api.js"></script>
|
<script src="https://www.google.com/recaptcha/api.js"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"></script>
|
||||||
@@ -122,5 +123,7 @@ moonlight.loading.registerXterm();
|
|||||||
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
|
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
|
||||||
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
|
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
|
||||||
|
|
||||||
|
<script src="/_framework/blazor.server.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -6,6 +6,7 @@ using Moonlight.App.ApiClients.CloudPanel;
|
|||||||
using Moonlight.App.ApiClients.Daemon;
|
using Moonlight.App.ApiClients.Daemon;
|
||||||
using Moonlight.App.ApiClients.Modrinth;
|
using Moonlight.App.ApiClients.Modrinth;
|
||||||
using Moonlight.App.ApiClients.Paper;
|
using Moonlight.App.ApiClients.Paper;
|
||||||
|
using Moonlight.App.ApiClients.Telemetry;
|
||||||
using Moonlight.App.ApiClients.Wings;
|
using Moonlight.App.ApiClients.Wings;
|
||||||
using Moonlight.App.Database;
|
using Moonlight.App.Database;
|
||||||
using Moonlight.App.Diagnostics.HealthChecks;
|
using Moonlight.App.Diagnostics.HealthChecks;
|
||||||
@@ -29,8 +30,9 @@ using Moonlight.App.Services.Notifications;
|
|||||||
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;
|
||||||
|
using Sentry;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Sinks.SystemConsole.Themes;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Moonlight
|
namespace Moonlight
|
||||||
{
|
{
|
||||||
@@ -40,24 +42,65 @@ namespace Moonlight
|
|||||||
{
|
{
|
||||||
// This will also copy all default config files
|
// This will also copy all default config files
|
||||||
var configService = new ConfigService(new StorageService());
|
var configService = new ConfigService(new StorageService());
|
||||||
|
var shouldUseSentry = configService
|
||||||
|
.Get()
|
||||||
|
.Moonlight.Sentry.Enable;
|
||||||
|
|
||||||
if (configService.DebugMode)
|
if (configService.DebugMode)
|
||||||
{
|
{
|
||||||
Log.Logger = new LoggerConfiguration()
|
if (shouldUseSentry)
|
||||||
.MinimumLevel.Verbose()
|
{
|
||||||
.Enrich.FromLogContext()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.WriteTo.Console(
|
.MinimumLevel.Verbose()
|
||||||
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
.Enrich.FromLogContext()
|
||||||
.CreateLogger();
|
.WriteTo.Console(
|
||||||
|
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
||||||
|
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
|
||||||
|
.WriteTo.Sentry(options =>
|
||||||
|
{
|
||||||
|
options.MinimumBreadcrumbLevel = LogEventLevel.Debug;
|
||||||
|
options.MinimumEventLevel = LogEventLevel.Warning;
|
||||||
|
})
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Verbose()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.Console(
|
||||||
|
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
||||||
|
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Logger = new LoggerConfiguration()
|
if (shouldUseSentry)
|
||||||
.MinimumLevel.Information()
|
{
|
||||||
.Enrich.FromLogContext()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.WriteTo.Console(
|
.MinimumLevel.Information()
|
||||||
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
.Enrich.FromLogContext()
|
||||||
.CreateLogger();
|
.WriteTo.Console(
|
||||||
|
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
||||||
|
.WriteTo.Sentry(options =>
|
||||||
|
{
|
||||||
|
options.MinimumBreadcrumbLevel = LogEventLevel.Information;
|
||||||
|
options.MinimumEventLevel = LogEventLevel.Warning;
|
||||||
|
})
|
||||||
|
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Information()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.Console(
|
||||||
|
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
||||||
|
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}");
|
Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}");
|
||||||
@@ -71,8 +114,8 @@ namespace Moonlight
|
|||||||
|
|
||||||
// Switch to logging.net injection
|
// Switch to logging.net injection
|
||||||
// TODO: Enable in production
|
// TODO: Enable in production
|
||||||
//builder.Logging.ClearProviders();
|
builder.Logging.ClearProviders();
|
||||||
//builder.Logging.AddProvider(new LogMigratorProvider());
|
builder.Logging.AddProvider(new LogMigratorProvider());
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
@@ -89,11 +132,27 @@ namespace Moonlight
|
|||||||
.AddCheck<NodeHealthCheck>("Nodes")
|
.AddCheck<NodeHealthCheck>("Nodes")
|
||||||
.AddCheck<DaemonHealthCheck>("Daemons");
|
.AddCheck<DaemonHealthCheck>("Daemons");
|
||||||
|
|
||||||
|
// Sentry
|
||||||
|
if (shouldUseSentry)
|
||||||
|
{
|
||||||
|
builder.WebHost.UseSentry(options =>
|
||||||
|
{
|
||||||
|
options.Dsn = configService
|
||||||
|
.Get()
|
||||||
|
.Moonlight.Sentry.Dsn;
|
||||||
|
|
||||||
|
options.Debug = configService.DebugMode;
|
||||||
|
options.DiagnosticLevel = SentryLevel.Warning;
|
||||||
|
options.TracesSampleRate = 1.0;
|
||||||
|
|
||||||
|
options.DiagnosticLogger = new SentryDiagnosticsLogger(SentryLevel.Warning);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Databases
|
// Databases
|
||||||
builder.Services.AddDbContext<DataContext>();
|
builder.Services.AddDbContext<DataContext>();
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
builder.Services.AddSingleton<SessionRepository>();
|
|
||||||
builder.Services.AddScoped<UserRepository>();
|
builder.Services.AddScoped<UserRepository>();
|
||||||
builder.Services.AddScoped<NodeRepository>();
|
builder.Services.AddScoped<NodeRepository>();
|
||||||
builder.Services.AddScoped<ServerRepository>();
|
builder.Services.AddScoped<ServerRepository>();
|
||||||
@@ -120,7 +179,6 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<CookieService>();
|
builder.Services.AddScoped<CookieService>();
|
||||||
builder.Services.AddScoped<IdentityService>();
|
builder.Services.AddScoped<IdentityService>();
|
||||||
builder.Services.AddScoped<IpLocateService>();
|
builder.Services.AddScoped<IpLocateService>();
|
||||||
builder.Services.AddScoped<SessionService>();
|
|
||||||
builder.Services.AddScoped<AlertService>();
|
builder.Services.AddScoped<AlertService>();
|
||||||
builder.Services.AddScoped<SmartTranslateService>();
|
builder.Services.AddScoped<SmartTranslateService>();
|
||||||
builder.Services.AddScoped<UserService>();
|
builder.Services.AddScoped<UserService>();
|
||||||
@@ -156,6 +214,9 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<SubscriptionService>();
|
builder.Services.AddScoped<SubscriptionService>();
|
||||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
builder.Services.AddScoped<SubscriptionAdminService>();
|
||||||
|
|
||||||
|
builder.Services.AddScoped<SessionClientService>();
|
||||||
|
builder.Services.AddSingleton<SessionServerService>();
|
||||||
|
|
||||||
// Loggers
|
// Loggers
|
||||||
builder.Services.AddScoped<MailService>();
|
builder.Services.AddScoped<MailService>();
|
||||||
builder.Services.AddSingleton<TrashMailDetectorService>();
|
builder.Services.AddSingleton<TrashMailDetectorService>();
|
||||||
@@ -176,6 +237,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<DaemonApiHelper>();
|
builder.Services.AddScoped<DaemonApiHelper>();
|
||||||
builder.Services.AddScoped<CloudPanelApiHelper>();
|
builder.Services.AddScoped<CloudPanelApiHelper>();
|
||||||
builder.Services.AddScoped<ModrinthApiHelper>();
|
builder.Services.AddScoped<ModrinthApiHelper>();
|
||||||
|
builder.Services.AddScoped<TelemetryApiHelper>();
|
||||||
|
|
||||||
// Background services
|
// Background services
|
||||||
builder.Services.AddSingleton<DiscordBotService>();
|
builder.Services.AddSingleton<DiscordBotService>();
|
||||||
@@ -183,13 +245,13 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<DiscordNotificationService>();
|
builder.Services.AddSingleton<DiscordNotificationService>();
|
||||||
builder.Services.AddSingleton<CleanupService>();
|
builder.Services.AddSingleton<CleanupService>();
|
||||||
builder.Services.AddSingleton<MalwareScanService>();
|
builder.Services.AddSingleton<MalwareScanService>();
|
||||||
|
builder.Services.AddSingleton<TelemetryService>();
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
builder.Services.AddSingleton<MoonlightService>();
|
builder.Services.AddSingleton<MoonlightService>();
|
||||||
|
|
||||||
// Third party services
|
// Third party services
|
||||||
builder.Services.AddBlazorTable();
|
builder.Services.AddBlazorTable();
|
||||||
builder.Services.AddSweetAlert2(options => { options.Theme = SweetAlertTheme.Dark; });
|
|
||||||
builder.Services.AddBlazorContextMenu();
|
builder.Services.AddBlazorContextMenu();
|
||||||
builder.Services.AddBlazorDownloadFile();
|
builder.Services.AddBlazorDownloadFile();
|
||||||
|
|
||||||
@@ -203,6 +265,12 @@ namespace Moonlight
|
|||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sentry
|
||||||
|
if (shouldUseSentry)
|
||||||
|
{
|
||||||
|
app.UseSentryTracing();
|
||||||
|
}
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
@@ -222,6 +290,7 @@ namespace Moonlight
|
|||||||
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
||||||
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
||||||
_ = app.Services.GetRequiredService<MalwareScanService>();
|
_ = app.Services.GetRequiredService<MalwareScanService>();
|
||||||
|
_ = app.Services.GetRequiredService<TelemetryService>();
|
||||||
|
|
||||||
_ = app.Services.GetRequiredService<MoonlightService>();
|
_ = app.Services.GetRequiredService<MoonlightService>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Files
|
||||||
|
|
||||||
@inject ConfigService ConfigService
|
@inject ResourceService ResourceService
|
||||||
|
|
||||||
@{
|
|
||||||
var moonlightConfig = ConfigService
|
|
||||||
.GetSection("Moonlight");
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="card card-flush w-lg-650px py-5">
|
<div class="card card-flush w-lg-650px py-5">
|
||||||
<div class="card-body py-15 py-lg-20">
|
<div class="card-body py-15 py-lg-20">
|
||||||
<div class="mb-14">
|
<div class="mb-14">
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logolong.png" class="h-40px">
|
<img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-40px">
|
||||||
</div>
|
</div>
|
||||||
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your account is banned from moonlight</TL></h1>
|
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your account is banned from moonlight</TL></h1>
|
||||||
<div class="fw-semibold fs-6 text-gray-500 mb-8">
|
<div class="fw-semibold fs-6 text-gray-500 mb-8">
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Files
|
||||||
|
|
||||||
@inject ConfigService ConfigService
|
@inject ResourceService ResourceService
|
||||||
|
|
||||||
@{
|
|
||||||
var moonlightConfig = ConfigService
|
|
||||||
.GetSection("Moonlight");
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="card card-flush w-lg-650px py-5">
|
<div class="card card-flush w-lg-650px py-5">
|
||||||
<div class="card-body py-15 py-lg-20">
|
<div class="card-body py-15 py-lg-20">
|
||||||
<div class="mb-14">
|
<div class="mb-14">
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logolong.png" class="h-40px">
|
<img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-40px">
|
||||||
</div>
|
</div>
|
||||||
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your moonlight account is disabled</TL></h1>
|
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your moonlight account is disabled</TL></h1>
|
||||||
<div class="fw-semibold fs-6 text-gray-500 mb-8">
|
<div class="fw-semibold fs-6 text-gray-500 mb-8">
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
{
|
{
|
||||||
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
|
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
|
||||||
<div class="fv-row mb-8 fv-plugins-icon-container">
|
<div class="fv-row mb-8 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent"></InputText>
|
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent" placeholder="@(SmartTranslateService.Translate("2fa code"))"></InputText>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid mb-10">
|
<div class="d-grid mb-10">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
@using Moonlight.App.Helpers.Files
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject ModalService ModalService
|
||||||
|
|
||||||
|
<div class="modal fade" id="connectionDetails" tabindex="-1" aria-labelledby="connectionDetails" style="display: none;" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<TL>Connection details</TL>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row fv-row mb-7">
|
||||||
|
<div class="col-md-3 text-md-start">
|
||||||
|
<label class="fs-6 fw-semibold form-label mt-3">
|
||||||
|
<TL>Host</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Host)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row fv-row mb-7">
|
||||||
|
<div class="col-md-3 text-md-start">
|
||||||
|
<label class="fs-6 fw-semibold form-label mt-3">
|
||||||
|
<TL>Port</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Port)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row fv-row mb-7">
|
||||||
|
<div class="col-md-3 text-md-start">
|
||||||
|
<label class="fs-6 fw-semibold form-label mt-3">
|
||||||
|
<TL>Username</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Username)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<TL>Close</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
|
private string Host = "";
|
||||||
|
private string Username = "";
|
||||||
|
private int Port;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Uri uri = new Uri(await Access.GetLaunchUrl());
|
||||||
|
|
||||||
|
Host = uri.Host;
|
||||||
|
Port = uri.Port;
|
||||||
|
Username = uri.UserInfo.Split(':')[0];
|
||||||
|
}
|
||||||
|
catch (NotImplementedException)
|
||||||
|
{
|
||||||
|
Host = "N/A";
|
||||||
|
Port = -1;
|
||||||
|
Username = "N/A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Show()
|
||||||
|
{
|
||||||
|
await ModalService.Show("connectionDetails");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ else
|
|||||||
<div class="card-header border-0 my-2">
|
<div class="card-header border-0 my-2">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<div class="d-flex flex-stack">
|
<div class="d-flex flex-stack">
|
||||||
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged" />
|
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-toolbar">
|
<div class="card-toolbar">
|
||||||
@@ -34,7 +34,9 @@ else
|
|||||||
@if (View != null && View.SelectedFiles.Any())
|
@if (View != null && View.SelectedFiles.Any())
|
||||||
{
|
{
|
||||||
<div class="fw-bold me-5">
|
<div class="fw-bold me-5">
|
||||||
<span class="me-2">@(View.SelectedFiles.Length) <TL>selected</TL></span>
|
<span class="me-2">
|
||||||
|
@(View.SelectedFiles.Length) <TL>selected</TL>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Move"))"
|
<WButton Text="@(SmartTranslateService.Translate("Move"))"
|
||||||
@@ -57,37 +59,46 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
|
<div class="btn-group me-3">
|
||||||
<span class="svg-icon svg-icon-muted svg-icon-2">
|
<button type="button" @onclick="Launch" class="btn btn-light-primary">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<TL>Launch WinSCP</TL>
|
||||||
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
|
</button>
|
||||||
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
|
<button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
</svg>
|
<span class="visually-hidden"></span>
|
||||||
</span>
|
</button>
|
||||||
<TL>Launch WinSCP</TL>
|
<ul class="dropdown-menu">
|
||||||
</button>
|
<li>
|
||||||
|
<a class="dropdown-item btn" target="_blank" href="https://winscp.net/eng/downloads.php">
|
||||||
|
<TL>Download WinSCP</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item btn" @onclick="() => ConnectionDetailsModal.Show()">
|
||||||
|
<TL>Show connection details</TL>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" @onclick="CreateFile" class="btn btn-light-primary me-3">
|
<div class="btn-group me-3">
|
||||||
<span class="svg-icon svg-icon-2">
|
<button type="button" class="btn btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<TL>New</TL>
|
||||||
<path fill="currentColor" d="M6 22h12a2 2 0 0 0 2-2V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2zm7-18 5 5h-5V4zM8 14h3v-3h2v3h3v2h-3v3h-2v-3H8v-2z"></path>
|
</button>
|
||||||
</svg>
|
<ul class="dropdown-menu">
|
||||||
</span>
|
<li>
|
||||||
<TL>New file</TL>
|
<button @onclick="CreateFile" class="dropdown-item btn">
|
||||||
</button>
|
<TL>New file</TL>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button @onclick="CreateFolder" class="dropdown-item btn">
|
||||||
|
<TL>New folder</TL>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
|
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
|
||||||
<span class="svg-icon svg-icon-2">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
|
||||||
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<TL>New folder</TL>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged" />
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,6 +121,8 @@ else
|
|||||||
Access="MoveAccess"
|
Access="MoveAccess"
|
||||||
OnSubmit="OnFileMoveSubmit">
|
OnSubmit="OnFileMoveSubmit">
|
||||||
</FileSelectModal>
|
</FileSelectModal>
|
||||||
|
|
||||||
|
<ConnectionDetailsModal @ref="ConnectionDetailsModal" Access="Access"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code
|
@code
|
||||||
@@ -135,6 +148,9 @@ else
|
|||||||
// Config
|
// Config
|
||||||
private ContextAction[] Actions = Array.Empty<ContextAction>();
|
private ContextAction[] Actions = Array.Empty<ContextAction>();
|
||||||
|
|
||||||
|
// Connection details
|
||||||
|
private ConnectionDetailsModal ConnectionDetailsModal;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
MoveAccess = (FileAccess)Access.Clone();
|
MoveAccess = (FileAccess)Access.Clone();
|
||||||
@@ -151,7 +167,7 @@ else
|
|||||||
SmartTranslateService.Translate("Rename"),
|
SmartTranslateService.Translate("Rename"),
|
||||||
SmartTranslateService.Translate("Enter a new name"),
|
SmartTranslateService.Translate("Enter a new name"),
|
||||||
x.Name
|
x.Name
|
||||||
);
|
);
|
||||||
|
|
||||||
if (name != x.Name)
|
if (name != x.Name)
|
||||||
{
|
{
|
||||||
@@ -162,7 +178,7 @@ else
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.Add(new ()
|
actions.Add(new()
|
||||||
{
|
{
|
||||||
Id = "download",
|
Id = "download",
|
||||||
Name = "Download",
|
Name = "Download",
|
||||||
@@ -200,7 +216,7 @@ else
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.Add(new ()
|
actions.Add(new()
|
||||||
{
|
{
|
||||||
Id = "decompress",
|
Id = "decompress",
|
||||||
Name = "Decompress",
|
Name = "Decompress",
|
||||||
@@ -305,7 +321,7 @@ else
|
|||||||
if (string.IsNullOrEmpty(name))
|
if (string.IsNullOrEmpty(name))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Access.Write(new FileData{IsFile = true, Name = name}, "");
|
await Access.Write(new FileData { IsFile = true, Name = name }, "");
|
||||||
await View!.Refresh();
|
await View!.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
65
Moonlight/Shared/Components/Forms/SmartFormClass.razor
Normal file
65
Moonlight/Shared/Components/Forms/SmartFormClass.razor
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
@using System.Reflection
|
||||||
|
@using System.Collections
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
|
<div class="accordion my-3" id="configSetting@(Model.GetHashCode())">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="configSetting-header@(Model.GetHashCode())">
|
||||||
|
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#configSetting-body@(Model.GetHashCode())" aria-expanded="false" aria-controls="configSetting-body@(Model.GetHashCode())">
|
||||||
|
@{
|
||||||
|
var name = Formatter.ReplaceEnd(Model.GetType().Name, "Data", "");
|
||||||
|
name = Formatter.ConvertCamelCaseToSpaces(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@(name)
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="configSetting-body@(Model.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="configSetting-header@(Model.GetHashCode())" data-bs-parent="#configSetting">
|
||||||
|
<div class="accordion-body">
|
||||||
|
@foreach (var property in Model.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
@BindAndRenderProperty(property)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public object Model { get; set; }
|
||||||
|
|
||||||
|
private RenderFragment BindAndRenderProperty(PropertyInfo property)
|
||||||
|
{
|
||||||
|
if (property.PropertyType.IsClass && !property.PropertyType.IsPrimitive && !typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
|
||||||
|
{
|
||||||
|
return @<SmartFormClass Model="@property.GetValue(Model)"/>;
|
||||||
|
|
||||||
|
// If the property is a subclass, serialize and generate form for it
|
||||||
|
/*
|
||||||
|
foreach (var subProperty in property.PropertyType.GetProperties())
|
||||||
|
{
|
||||||
|
return BindAndRenderProperty(subProperty);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
else if (property.PropertyType == typeof(int) || property.PropertyType == typeof(string) || property.PropertyType == typeof(bool) || property.PropertyType == typeof(decimal) || property.PropertyType == typeof(long))
|
||||||
|
{
|
||||||
|
return @<SmartFormProperty Model="Model" PropertyInfo="property"/>;
|
||||||
|
}
|
||||||
|
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
|
||||||
|
{
|
||||||
|
// If the property is a collection, generate form for each element
|
||||||
|
var collection = property.GetValue(Model) as IEnumerable;
|
||||||
|
if (collection != null)
|
||||||
|
{
|
||||||
|
foreach (var element in collection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Additional property types could be handled here (e.g., DateTime, int, etc.)
|
||||||
|
|
||||||
|
return @<div></div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
Moonlight/Shared/Components/Forms/SmartFormProperty.razor
Normal file
79
Moonlight/Shared/Components/Forms/SmartFormProperty.razor
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
@using System.Reflection
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using System.ComponentModel
|
||||||
|
|
||||||
|
<label class="form-label" for="@PropertyInfo.Name">
|
||||||
|
@(Formatter.ConvertCamelCaseToSpaces(PropertyInfo.Name))
|
||||||
|
</label>
|
||||||
|
@{
|
||||||
|
//TODO: Tidy up this code
|
||||||
|
|
||||||
|
var attrs = PropertyInfo.GetCustomAttributes(true);
|
||||||
|
|
||||||
|
var descAttr = attrs
|
||||||
|
.FirstOrDefault(x => x.GetType() == typeof(DescriptionAttribute));
|
||||||
|
|
||||||
|
var blurBool = attrs.Any(x => x.GetType() == typeof(BlurAttribute));
|
||||||
|
var blur = blurBool ? "blur-unless-hover" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (descAttr != null)
|
||||||
|
{
|
||||||
|
var a = descAttr as DescriptionAttribute;
|
||||||
|
|
||||||
|
<div class="form-text fs-5 mb-2 mt-0">
|
||||||
|
@(a.Description)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
@if (PropertyInfo.PropertyType == typeof(string))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<div class="@(blur) w-100">
|
||||||
|
<InputText id="@PropertyInfo.Name" @bind-Value="binder.StringValue" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(int))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputNumber id="@PropertyInfo.Name" @bind-Value="binder.IntValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(long))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputNumber id="@PropertyInfo.Name" @bind-Value="binder.LongValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(bool))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox id="@PropertyInfo.Name" @bind-Value="binder.BoolValue" class="form-check-input"/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(DateTime))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputDate id="@PropertyInfo.Name" @bind-Value="binder.DateTimeValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(decimal))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputNumber id="@PropertyInfo.Name" step="0.01" @bind-Value="binder.DoubleValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public PropertyInfo PropertyInfo { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public object Model { get; set; }
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<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 == 1 ? "active" : "")" href="/admin/system/logs">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/system/sentry">
|
||||||
<TL>Logs</TL>
|
<TL>Sentry</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item mt-2">
|
<li class="nav-item mt-2">
|
||||||
@@ -39,6 +39,11 @@
|
|||||||
<TL>News</TL>
|
<TL>News</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 == 8 ? "active" : "")" href="/admin/system/configuration">
|
||||||
|
<TL>Configuration</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
|
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<a class="text-gray-900 fs-2 fw-bold me-1">@(User.FirstName) @(User.LastName)</a>
|
<a class="text-gray-900 fs-2 fw-bold me-1 @(User.StreamerMode ? "blur" : "")">@(User.FirstName) @(User.LastName)</a>
|
||||||
|
|
||||||
@if (User.Status == UserStatus.Verified)
|
@if (User.Status == UserStatus.Verified)
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
|
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
|
||||||
<span class="d-flex align-items-center text-gray-400 mb-2">
|
<span class="d-flex align-items-center text-gray-400 mb-2 @(User.StreamerMode ? "blur" : "")">
|
||||||
@(User.Email)
|
@(User.Email)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,31 +4,31 @@
|
|||||||
|
|
||||||
@{
|
@{
|
||||||
var marketingConfig = ConfigService
|
var marketingConfig = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Marketing");
|
.Moonlight.Marketing;
|
||||||
}
|
}
|
||||||
|
|
||||||
<div id="kt_app_footer" class="app-footer">
|
<div id="kt_app_footer" class="app-footer">
|
||||||
<div class="app-container container-fluid d-flex flex-column flex-md-row flex-center flex-md-stack py-3">
|
<div class="app-container container-fluid d-flex flex-column flex-md-row flex-center flex-md-stack py-3">
|
||||||
<div class="text-dark order-2 order-md-1">
|
<div class="text-dark order-2 order-md-1">
|
||||||
<span class="text-muted fw-semibold me-1">2022 - @DateTime.Now.Year©</span>
|
<span class="text-muted fw-semibold me-1">2022 - @DateTime.Now.Year©</span>
|
||||||
<a href="@(marketingConfig.GetValue<string>("Website"))" target="_blank" class="text-gray-800 text-hover-primary">
|
<a href="@(marketingConfig.Website)" target="_blank" class="text-gray-800 text-hover-primary">
|
||||||
@(marketingConfig.GetValue<string>("BrandName"))
|
@(marketingConfig.BrandName)
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<ul class="menu menu-gray-600 menu-hover-primary fw-semibold order-1">
|
<ul class="menu menu-gray-600 menu-hover-primary fw-semibold order-1">
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a href="@(marketingConfig.GetValue<string>("About"))" target="_blank" class="menu-link px-2">
|
<a href="@(marketingConfig.About)" target="_blank" class="menu-link px-2">
|
||||||
<TL>About us</TL>
|
<TL>About us</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a href="@(marketingConfig.GetValue<string>("Imprint"))" target="_blank" class="menu-link px-2">
|
<a href="@(marketingConfig.Imprint)" target="_blank" class="menu-link px-2">
|
||||||
<TL>Imprint</TL>
|
<TL>Imprint</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a href="@(marketingConfig.GetValue<string>("Privacy"))" target="_blank" class="menu-link px-2">
|
<a href="@(marketingConfig.Privacy)" target="_blank" class="menu-link px-2">
|
||||||
<TL>Privacy</TL>
|
<TL>Privacy</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -69,14 +69,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="fw-bold d-flex align-items-center fs-5">
|
<div class="fw-bold d-flex align-items-center fs-5">
|
||||||
@(User.FirstName) @(User.LastName)
|
<div class="@(User.StreamerMode ? "blur" : "")">
|
||||||
|
@(User.FirstName) @(User.LastName)
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (User.Admin)
|
@if (User.Admin)
|
||||||
{
|
{
|
||||||
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
|
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<a class="fw-semibold text-muted text-hover-primary fs-7">@(User.Email)</a>
|
<a class="fw-semibold text-muted text-hover-primary fs-7 @(User.StreamerMode ? "blur" : "")">@(User.Email)</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services.Files
|
||||||
|
|
||||||
@inject ConfigService ConfigService
|
@inject ResourceService ResourceService
|
||||||
|
|
||||||
@{
|
|
||||||
var moonlightConfig = ConfigService.GetSection("Moonlight");
|
|
||||||
}
|
|
||||||
|
|
||||||
<div id="kt_app_header" class="app-header">
|
<div id="kt_app_header" class="app-header">
|
||||||
<div class="app-container container-fluid d-flex align-items-stretch justify-content-between">
|
<div class="app-container container-fluid d-flex align-items-stretch justify-content-between">
|
||||||
@@ -15,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center flex-grow-1 flex-lg-grow-0">
|
<div class="d-flex align-items-center flex-grow-1 flex-lg-grow-0">
|
||||||
<a href="/" class="d-lg-none">
|
<a href="/" class="d-lg-none">
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-30px"/>
|
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))" class="h-30px"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-stretch justify-content-between flex-lg-grow-1" id="kt_app_header_wrapper">
|
<div class="d-flex align-items-stretch justify-content-between flex-lg-grow-1" id="kt_app_header_wrapper">
|
||||||
|
|||||||
@@ -1,32 +1,28 @@
|
|||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Files
|
||||||
|
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
@inject ConfigService ConfigService
|
@inject ResourceService ResourceService
|
||||||
@inject IJSRuntime JsRuntime
|
@inject IJSRuntime JsRuntime
|
||||||
|
|
||||||
@{
|
|
||||||
var moonlightConfig = ConfigService
|
|
||||||
.GetSection("Moonlight");
|
|
||||||
}
|
|
||||||
|
|
||||||
<div id="kt_app_sidebar" class="app-sidebar flex-column" data-kt-drawer="true" data-kt-drawer-name="app-sidebar" data-kt-drawer-activate="{default: true, lg: false}" data-kt-drawer-overlay="true" data-kt-drawer-width="225px" data-kt-drawer-direction="start" data-kt-drawer-toggle="#kt_app_sidebar_mobile_toggle">
|
<div id="kt_app_sidebar" class="app-sidebar flex-column" data-kt-drawer="true" data-kt-drawer-name="app-sidebar" data-kt-drawer-activate="{default: true, lg: false}" data-kt-drawer-overlay="true" data-kt-drawer-width="225px" data-kt-drawer-direction="start" data-kt-drawer-toggle="#kt_app_sidebar_mobile_toggle">
|
||||||
<div class="app-sidebar-logo px-6" id="kt_app_sidebar_logo">
|
<div class="app-sidebar-logo px-6" id="kt_app_sidebar_logo">
|
||||||
<a href="@(User != null ? "/" : "/login")">
|
<a href="@(User != null ? "/" : "/login")">
|
||||||
@if (sidebar == "dark-sidebar")
|
@if (sidebar == "dark-sidebar")
|
||||||
{
|
{
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logolong.png" class="h-45px app-sidebar-logo-default"/>
|
<img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-45px app-sidebar-logo-default"/>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (sidebar == "light-sidebar")
|
if (sidebar == "light-sidebar")
|
||||||
{
|
{
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="theme-light-show h-20px app-sidebar-logo-default"/>
|
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))" class="theme-light-show h-20px app-sidebar-logo-default"/>
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="theme-dark-show h-20px app-sidebar-logo-default"/>
|
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))" class="theme-dark-show h-20px app-sidebar-logo-default"/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-20px app-sidebar-logo-minimize"/>
|
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))" class="h-20px app-sidebar-logo-minimize"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div id="kt_app_sidebar_toggle" class="app-sidebar-toggle btn btn-icon btn-shadow btn-sm btn-color-muted btn-active-color-primary body-bg h-30px w-30px position-absolute top-50 start-100 translate-middle rotate" data-kt-toggle="true" data-kt-toggle-state="active" data-kt-toggle-target="body" data-kt-toggle-name="app-sidebar-minimize">
|
<div id="kt_app_sidebar_toggle" class="app-sidebar-toggle btn btn-icon btn-shadow btn-sm btn-color-muted btn-active-color-primary body-bg h-30px w-30px position-absolute top-50 start-100 translate-middle rotate" data-kt-toggle="true" data-kt-toggle-state="active" data-kt-toggle-target="body" data-kt-toggle-name="app-sidebar-minimize">
|
||||||
@@ -38,7 +34,7 @@
|
|||||||
|
|
||||||
<div class="app-sidebar-footer flex-column-auto pt-2 pb-6 px-6" id="kt_app_sidebar_footer">
|
<div class="app-sidebar-footer flex-column-auto pt-2 pb-6 px-6" id="kt_app_sidebar_footer">
|
||||||
<a href="/support" class="btn btn-flex flex-center btn-custom btn-primary overflow-hidden text-nowrap px-0 h-40px w-100 btn-label">
|
<a href="/support" class="btn btn-flex flex-center btn-custom btn-primary overflow-hidden text-nowrap px-0 h-40px w-100 btn-label">
|
||||||
<TL>Open support</TL>
|
<i class="bx bx-sm bx-support"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
@using Moonlight.App.Services
|
|
||||||
|
|
||||||
@inject ConfigService ConfigService
|
|
||||||
|
|
||||||
@{
|
|
||||||
var setupComplete = ConfigService
|
|
||||||
.GetSection("Moonlight")
|
|
||||||
.GetValue<bool>("SetupComplete");
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (!setupComplete)
|
|
||||||
{
|
|
||||||
@ChildContent
|
|
||||||
}
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
[Parameter]
|
|
||||||
public RenderFragment ChildContent { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
@using Moonlight.App.Services
|
|
||||||
|
|
||||||
@inject ConfigService ConfigService
|
|
||||||
|
|
||||||
@{
|
|
||||||
var setupComplete = ConfigService
|
|
||||||
.GetSection("Moonlight")
|
|
||||||
.GetValue<bool>("SetupComplete");
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (setupComplete)
|
|
||||||
{
|
|
||||||
@ChildContent
|
|
||||||
}
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
[Parameter]
|
|
||||||
public RenderFragment ChildContent { get; set; }
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,15 @@
|
|||||||
|
|
||||||
public async void Dispose()
|
public async void Dispose()
|
||||||
{
|
{
|
||||||
await Xterm.DisposeAsync();
|
try
|
||||||
|
{
|
||||||
|
await Xterm.DisposeAsync();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignore dispose errors. They occur when the tab closes unexpectedly
|
||||||
|
// so we can ignore them
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnFirstRender()
|
private async void OnFirstRender()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
@inject IJSRuntime JsRuntime
|
@inject IJSRuntime JsRuntime
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
@inject SessionService SessionService
|
@inject SessionClientService SessionClientService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject EventSystem Event
|
@inject EventSystem Event
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@@ -163,13 +163,11 @@
|
|||||||
|
|
||||||
private bool IsIpBanned = false;
|
private bool IsIpBanned = false;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
AddBodyAttribute("data-kt-app-page-loading", "on");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAfterRender(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
{
|
||||||
|
if(firstRender)
|
||||||
|
AddBodyAttribute("data-kt-app-page-loading", "on");
|
||||||
|
|
||||||
//Initialize classes and attributes for layout with dark sidebar
|
//Initialize classes and attributes for layout with dark sidebar
|
||||||
AddBodyAttribute("data-kt-app-reset-transition", "true");
|
AddBodyAttribute("data-kt-app-reset-transition", "true");
|
||||||
|
|
||||||
@@ -217,12 +215,10 @@
|
|||||||
}
|
}
|
||||||
catch (Exception){ /* ignore errors to make sure that the session call is executed */ }
|
catch (Exception){ /* ignore errors to make sure that the session call is executed */ }
|
||||||
|
|
||||||
await SessionService.Register();
|
await SessionClientService.Start();
|
||||||
|
|
||||||
NavigationManager.LocationChanged += async (_, _) =>
|
NavigationManager.LocationChanged += async (_, _) =>
|
||||||
{
|
{
|
||||||
SessionService.Refresh();
|
|
||||||
|
|
||||||
if (!NavigationManager.Uri.Contains("/server/"))
|
if (!NavigationManager.Uri.Contains("/server/"))
|
||||||
await DynamicBackgroundService.Reset();
|
await DynamicBackgroundService.Reset();
|
||||||
};
|
};
|
||||||
@@ -257,7 +253,7 @@
|
|||||||
|
|
||||||
public async void Dispose()
|
public async void Dispose()
|
||||||
{
|
{
|
||||||
SessionService.Close();
|
await SessionClientService.Stop();
|
||||||
|
|
||||||
await KeyListenerService.DisposeAsync();
|
await KeyListenerService.DisposeAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
@inject IJSRuntime JsRuntime
|
@inject IJSRuntime JsRuntime
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
@inject SessionService SessionService
|
@inject SessionClientService SessionClientService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<GlobalErrorBoundary>
|
<GlobalErrorBoundary>
|
||||||
@@ -106,9 +106,7 @@
|
|||||||
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
|
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
|
||||||
await JsRuntime.InvokeVoidAsync("createSnow");
|
await JsRuntime.InvokeVoidAsync("createSnow");
|
||||||
|
|
||||||
await SessionService.Register();
|
await SessionClientService.Start();
|
||||||
|
|
||||||
NavigationManager.LocationChanged += (sender, args) => { SessionService.Refresh(); };
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -117,9 +115,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public async void Dispose()
|
||||||
{
|
{
|
||||||
SessionService.Close();
|
await SessionClientService.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddBodyAttribute(string attribute, string value)
|
private void AddBodyAttribute(string attribute, string value)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@inherits LayoutComponentBase
|
@using Moonlight.App.Extensions
|
||||||
|
@inherits LayoutComponentBase
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
@@ -14,23 +15,23 @@ window.emptyBody = function(){
|
|||||||
{
|
{
|
||||||
protected override void OnAfterRender(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
{
|
||||||
JS.InvokeVoidAsync("KTThemeMode.init");
|
JS.InvokeVoidSafe("KTThemeMode.init");
|
||||||
JS.InvokeVoidAsync("emptyBody");
|
JS.InvokeVoidSafe("emptyBody");
|
||||||
|
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
JS.InvokeVoidAsync("scrollTo", 0, 0);
|
JS.InvokeVoidSafe("scrollTo", 0, 0);
|
||||||
JS.InvokeVoidAsync("KTDialer.init");
|
JS.InvokeVoidSafe("KTDialer.init");
|
||||||
JS.InvokeVoidAsync("KTDrawer.init");
|
JS.InvokeVoidSafe("KTDrawer.init");
|
||||||
JS.InvokeVoidAsync("KTMenu.init");
|
JS.InvokeVoidSafe("KTMenu.init");
|
||||||
JS.InvokeVoidAsync("KTImageInput.init");
|
JS.InvokeVoidSafe("KTImageInput.init");
|
||||||
JS.InvokeVoidAsync("KTPasswordMeter.init");
|
JS.InvokeVoidSafe("KTPasswordMeter.init");
|
||||||
JS.InvokeVoidAsync("KTScroll.init");
|
JS.InvokeVoidSafe("KTScroll.init");
|
||||||
JS.InvokeVoidAsync("KTScrolltop.init");
|
JS.InvokeVoidSafe("KTScrolltop.init");
|
||||||
JS.InvokeVoidAsync("KTSticky.init");
|
JS.InvokeVoidSafe("KTSticky.init");
|
||||||
JS.InvokeVoidAsync("KTSwapper.init");
|
JS.InvokeVoidSafe("KTSwapper.init");
|
||||||
JS.InvokeVoidAsync("KTToggle.init");
|
JS.InvokeVoidSafe("KTToggle.init");
|
||||||
JS.InvokeVoidAsync("KTMenu.updateByLinkAttribute", $"/{NavigationManager.ToBaseRelativePath(NavigationManager.Uri)}");
|
JS.InvokeVoidSafe("KTMenu.updateByLinkAttribute", $"/{NavigationManager.ToBaseRelativePath(NavigationManager.Uri)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
JS.InvokeVoidAsync("KTLayoutSearch.init");
|
JS.InvokeVoidAsync("KTLayoutSearch.init");
|
||||||
@@ -45,30 +46,18 @@ window.emptyBody = function(){
|
|||||||
|
|
||||||
private async void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
private async void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
||||||
{
|
{
|
||||||
await InvokeJsSave("scrollTo", 0, 0);
|
await JS.InvokeVoidSafeAsync("scrollTo", 0, 0);
|
||||||
await InvokeJsSave("KTDrawer.createInstances");
|
await JS.InvokeVoidSafeAsync("KTDrawer.createInstances");
|
||||||
await InvokeJsSave("KTMenu.createInstances");
|
await JS.InvokeVoidSafeAsync("KTMenu.createInstances");
|
||||||
await InvokeJsSave("KTImageInput.createInstances");
|
await JS.InvokeVoidSafeAsync("KTImageInput.createInstances");
|
||||||
await InvokeJsSave("KTPasswordMeter.createInstances");
|
await JS.InvokeVoidSafeAsync("KTPasswordMeter.createInstances");
|
||||||
await InvokeJsSave("KTScroll.createInstances");
|
await JS.InvokeVoidSafeAsync("KTScroll.createInstances");
|
||||||
await InvokeJsSave("KTScrolltop.createInstances");
|
await JS.InvokeVoidSafeAsync("KTScrolltop.createInstances");
|
||||||
await InvokeJsSave("KTSticky.createInstances");
|
await JS.InvokeVoidSafeAsync("KTSticky.createInstances");
|
||||||
await InvokeJsSave("KTSwapper.createInstances");
|
await JS.InvokeVoidSafeAsync("KTSwapper.createInstances");
|
||||||
await InvokeJsSave("KTToggle.createInstances");
|
await JS.InvokeVoidSafeAsync("KTToggle.createInstances");
|
||||||
await InvokeJsSave("KTMenu.updateByLinkAttribute", $"/{NavigationManager.ToBaseRelativePath(args.Location)}");
|
await JS.InvokeVoidSafeAsync("KTMenu.updateByLinkAttribute", $"/{NavigationManager.ToBaseRelativePath(args.Location)}");
|
||||||
await InvokeJsSave("KTAppSidebar.init");
|
await JS.InvokeVoidSafeAsync("KTAppSidebar.init");
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InvokeJsSave(string method, params object[] args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JS.InvokeVoidAsync(method, args);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -151,8 +151,8 @@
|
|||||||
await lazyLoader.SetText("Loading health check data");
|
await lazyLoader.SetText("Loading health check data");
|
||||||
|
|
||||||
var appUrl = ConfigService
|
var appUrl = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetValue<string>("AppUrl");
|
.Moonlight.AppUrl;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
@inject NodeRepository NodeRepository
|
@inject NodeRepository NodeRepository
|
||||||
@inject SmartTranslateService SmartTranslateService
|
|
||||||
@inject ConfigService ConfigService
|
@inject ConfigService ConfigService
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var appUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
var appUrl = ConfigService.Get().Moonlight.AppUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
@@ -42,7 +40,7 @@
|
|||||||
host: 0.0.0.0<br/>
|
host: 0.0.0.0<br/>
|
||||||
port: @(Node.HttpPort)<br/>
|
port: @(Node.HttpPort)<br/>
|
||||||
ssl:<br/>
|
ssl:<br/>
|
||||||
enabled: false<br/>
|
enabled: @(Node.Ssl ? "true" : "false")<br/>
|
||||||
cert: /etc/letsencrypt/live/@(Node.Fqdn)/fullchain.pem<br/>
|
cert: /etc/letsencrypt/live/@(Node.Fqdn)/fullchain.pem<br/>
|
||||||
key: /etc/letsencrypt/live/@(Node.Fqdn)/privkey.pem<br/>
|
key: /etc/letsencrypt/live/@(Node.Fqdn)/privkey.pem<br/>
|
||||||
disable_remote_download: false<br/>
|
disable_remote_download: false<br/>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<TL>New image</TL>
|
<TL>New image</TL>
|
||||||
</a>
|
</a>
|
||||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||||
<label for="fileUpload" class="btn btn-sm btn-light-primary">
|
<label for="fileUpload" class="btn btn-sm btn-light-primary me-3">
|
||||||
<span class="svg-icon svg-icon-2">
|
<span class="svg-icon svg-icon-2">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||||
@@ -42,6 +42,17 @@
|
|||||||
</span>
|
</span>
|
||||||
<TL>Import</TL>
|
<TL>Import</TL>
|
||||||
</label>
|
</label>
|
||||||
|
<InputFile OnChange="OnEggFileChanged" type="file" id="eggFileUpload" hidden="" multiple=""/>
|
||||||
|
<label for="eggFileUpload" class="btn btn-sm btn-light-primary">
|
||||||
|
<span class="svg-icon svg-icon-2">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||||
|
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||||
|
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<TL>Import pterodactyl egg</TL>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body pt-0">
|
<div class="card-body pt-0">
|
||||||
@@ -173,4 +184,44 @@
|
|||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnEggFileChanged(InputFileChangeEventArgs arg)
|
||||||
|
{
|
||||||
|
var b = await AlertService.YesNo(
|
||||||
|
SmartTranslateService.Translate("Attention"),
|
||||||
|
SmartTranslateService.Translate("Imported pterodactyl eggs can result in broken images. We do not support pterodactyl eggs"),
|
||||||
|
SmartTranslateService.Translate("I take the risk"),
|
||||||
|
SmartTranslateService.Translate("Cancel")
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!b)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var browserFile in arg.GetMultipleFiles())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stream = browserFile.OpenReadStream(1024 * 1024 * 100);
|
||||||
|
var data = new byte[browserFile.Size];
|
||||||
|
_ = await stream.ReadAsync(data, 0, data.Length);
|
||||||
|
|
||||||
|
var json = Encoding.UTF8.GetString(data);
|
||||||
|
|
||||||
|
var image = EggConverter.Convert(json);
|
||||||
|
|
||||||
|
ImageRepository.Add(image);
|
||||||
|
|
||||||
|
await AlertService.Success(SmartTranslateService.Translate("Successfully imported image"));
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await AlertService.Error(SmartTranslateService.Translate("An unknown error occured while uploading and importing the image"));
|
||||||
|
Logger.Error("Error importing image");
|
||||||
|
Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
53
Moonlight/Shared/Views/Admin/Sys/Configuration.razor
Normal file
53
Moonlight/Shared/Views/Admin/Sys/Configuration.razor
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
@page "/admin/system/configuration"
|
||||||
|
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using Moonlight.App.Configuration
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject ConfigService ConfigService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<AdminSystemNavigation Index="8"/>
|
||||||
|
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
<div class="card">
|
||||||
|
<SmartForm Model="Config" OnValidSubmit="OnSubmit">
|
||||||
|
<div class="card-body">
|
||||||
|
<SmartFormClass Model="Config"/>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="text-end">
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<TL>Save</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SmartForm>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private ConfigV1 Config;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
Config = ConfigService.Get();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSubmit()
|
||||||
|
{
|
||||||
|
ConfigService.Save(Config);
|
||||||
|
await ToastService.Success(
|
||||||
|
SmartTranslateService.Translate(
|
||||||
|
"Successfully saved and reloaded configuration. Some changes may take affect after a restart of moonlight"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
@page "/admin/system/logs"
|
|
||||||
|
|
||||||
@using BlazorTable
|
|
||||||
@using Moonlight.App.Models.Misc
|
|
||||||
@using Moonlight.App.Services
|
|
||||||
@using Moonlight.Shared.Components.Navigations
|
|
||||||
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
|
||||||
|
|
||||||
<OnlyAdmin>
|
|
||||||
<AdminSystemNavigation Index="1"/>
|
|
||||||
</OnlyAdmin>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
private Task Load(LazyLoader arg)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
Moonlight/Shared/Views/Admin/Sys/Sentry.razor
Normal file
40
Moonlight/Shared/Views/Admin/Sys/Sentry.razor
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@page "/admin/system/sentry"
|
||||||
|
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using global::Sentry
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<AdminSystemNavigation Index="1"/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xxl-6 my-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<TL>Status</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="fs-5">
|
||||||
|
@if (SentrySdk.IsEnabled)
|
||||||
|
{
|
||||||
|
<TL>Sentry is enabled</TL>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TL>Sentry is disabled</TL>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
@inject UserRepository UserRepository
|
@inject UserRepository UserRepository
|
||||||
@inject UserService UserService
|
@inject UserService UserService
|
||||||
@inject SessionService SessionService
|
@inject SessionServerService SessionServerService
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
user.Status = User!.Status;
|
user.Status = User!.Status;
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
SessionService.ReloadUserSessions(User);
|
await SessionServerService.ReloadUserSessions(User);
|
||||||
|
|
||||||
await ToastService.Success(SmartTranslateService.Translate("Successfully updated user"));
|
await ToastService.Success(SmartTranslateService.Translate("Successfully updated user"));
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
await UserService.ChangePassword(User!, NewPassword, true);
|
await UserService.ChangePassword(User!, NewPassword, true);
|
||||||
NewPassword = "";
|
NewPassword = "";
|
||||||
|
|
||||||
SessionService.ReloadUserSessions(User);
|
await SessionServerService.ReloadUserSessions(User!);
|
||||||
|
|
||||||
await ToastService.Success(SmartTranslateService.Translate("Successfully updated password"));
|
await ToastService.Success(SmartTranslateService.Translate("Successfully updated password"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
@inject SessionService SessionService
|
@inject SessionServerService SessionServerService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<AdminSessionNavigation Index="1"/>
|
<AdminSessionNavigation Index="1"/>
|
||||||
@@ -44,8 +45,8 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Table TableItem="Session" Items="AllSessions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
<Table TableItem="SessionClientService" Items="AllSessions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Email)" Sortable="true" Filterable="true" Width="20%">
|
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Email)" Sortable="true" Filterable="true" Width="20%">
|
||||||
<Template>
|
<Template>
|
||||||
@if (context.User == null)
|
@if (context.User == null)
|
||||||
{
|
{
|
||||||
@@ -57,25 +58,33 @@
|
|||||||
}
|
}
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Ip)" Sortable="true" Filterable="true" Width="10%"/>
|
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.Url)" Sortable="true" Filterable="true" Width="10%"/>
|
<Template>
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Device)" Sortable="true" Filterable="true" Width="10%"/>
|
@(context.Ip)
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true" Width="10%">
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.NavigationManager.Uri)" Sortable="true" Filterable="true" Width="10%"/>
|
||||||
|
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||||
|
<Template>
|
||||||
|
@(context.Device)
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreateTimestamp)" Sortable="true" Filterable="true" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
@{
|
@{
|
||||||
var time = Formatter.FormatUptime((DateTime.UtcNow - context.CreatedAt).TotalMilliseconds);
|
var time = Formatter.FormatUptime((DateTime.UtcNow - context.CreateTimestamp).TotalMilliseconds);
|
||||||
}
|
}
|
||||||
<span>@(time)</span>
|
<span>@(time)</span>
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
<button @onclick="() => Navigate(context)" class="btn btn-sm btn-primary">
|
<button @onclick="() => Navigate(context)" class="btn btn-sm btn-primary">
|
||||||
<TL>Change url</TL>
|
<TL>Change url</TL>
|
||||||
</button>
|
</button>
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Session" Title="" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
<Column TableItem="SessionClientService" Title="" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
<button @onclick="() => Message(context)" class="btn btn-sm btn-warning">
|
<button @onclick="() => Message(context)" class="btn btn-sm btn-warning">
|
||||||
<TL>Message</TL>
|
<TL>Message</TL>
|
||||||
@@ -92,11 +101,11 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private Session[]? AllSessions;
|
private SessionClientService[]? AllSessions;
|
||||||
|
|
||||||
private Task Load(LazyLoader arg)
|
private async Task Load(LazyLoader arg)
|
||||||
{
|
{
|
||||||
AllSessions = SessionService.GetAll();
|
AllSessions = await SessionServerService.GetSessions();
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -109,35 +118,30 @@
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Error autorefreshing sessions");
|
Logger.Warn("Error auto refreshing sessions");
|
||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
AllSessions = SessionService.GetAll();
|
AllSessions = await SessionServerService.GetSessions();
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Navigate(Session session)
|
private async Task Navigate(SessionClientService session)
|
||||||
{
|
{
|
||||||
var url = await AlertService.Text("URL", SmartTranslateService.Translate("Enter url"), "");
|
var url = await AlertService.Text("URL", SmartTranslateService.Translate("Enter url"), "");
|
||||||
|
|
||||||
if (url == null)
|
if (string.IsNullOrEmpty(url))
|
||||||
return;
|
|
||||||
|
|
||||||
if (url == "")
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (url == "null")
|
if (url == "null")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
session.Navigation.NavigateTo(url, true);
|
session.NavigationManager.NavigateTo(url, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task MessageAll()
|
private async Task MessageAll()
|
||||||
@@ -157,22 +161,30 @@
|
|||||||
|
|
||||||
if (b)
|
if (b)
|
||||||
{
|
{
|
||||||
foreach (var session in SessionService.GetAll())
|
foreach (var session in AllSessions!)
|
||||||
{
|
{
|
||||||
try
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await session.AlertService.Warning("Admin Message", message);
|
try
|
||||||
}
|
{
|
||||||
catch (Exception e)
|
await session.AlertService.Warning("Admin Message", message);
|
||||||
{
|
}
|
||||||
Logger.Warn("Error sending user a alert");
|
catch (Exception e)
|
||||||
Logger.Warn(e);
|
{
|
||||||
}
|
Logger.Warn("Error sending user a alert");
|
||||||
|
Logger.Warn(e);
|
||||||
|
|
||||||
|
var translation = SmartTranslateService.Translate("An unknown error occured while sending admin message to user: ");
|
||||||
|
var identifier = session.User != null ? session.User.Email : session.Uuid.ToString();
|
||||||
|
|
||||||
|
await ToastService.Warning(translation + identifier);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Message(Session session)
|
private async Task Message(SessionClientService session)
|
||||||
{
|
{
|
||||||
var message = await AlertService.Text(
|
var message = await AlertService.Text(
|
||||||
SmartTranslateService.Translate("Enter message"),
|
SmartTranslateService.Translate("Enter message"),
|
||||||
@@ -191,12 +203,14 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await session.AlertService.Warning("Admin Message", message);
|
await session.AlertService.Warning("Admin Message", message!);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Error sending user a alert");
|
Logger.Warn("Error sending user a alert");
|
||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
|
|
||||||
|
await ToastService.Warning(SmartTranslateService.Translate("An unknown error occured while sending admin message"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,198 +21,222 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card card-body mb-5">
|
<div class="card card-body mb-5">
|
||||||
<div class="d-flex flex-column align-items-center text-center">
|
<div class="d-flex flex-column align-items-center text-center">
|
||||||
<img src="/api/moonlight/avatar/@(User.Id)" class="rounded-circle" alt="Profile picture" width="150">
|
<img src="/api/moonlight/avatar/@(User.Id)" class="rounded-circle" alt="Profile picture" width="150">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card card-body mb-5">
|
|
||||||
<div class="btn-group">
|
|
||||||
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)"><TL>Edit</TL></a>
|
|
||||||
<a class="btn btn-secondary" href="/admin/users"><TL>Back to list</TL></a>
|
|
||||||
<a class="btn btn-primary" href="/admin/support/view/@(User.Id)"><TL>Open support</TL></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card card-xl-stretch mb-5">
|
|
||||||
<div class="card-header border-0">
|
|
||||||
<h3 class="card-title fw-bold text-dark">
|
|
||||||
<TL>Servers</TL>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body pt-2">
|
|
||||||
@foreach (var server in Servers)
|
|
||||||
{
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<a href="/server/@(server.Uuid)" class="fs-6">@(server.Name) - @(server.Image.Name)</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
if (server != Servers.Last())
|
|
||||||
{
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card card-xl-stretch">
|
|
||||||
<div class="card-header border-0">
|
|
||||||
<h3 class="card-title fw-bold text-dark">
|
|
||||||
<TL>Domains</TL>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body pt-2">
|
|
||||||
@foreach (var domain in Domains)
|
|
||||||
{
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<a href="/domain/@(domain.Id)" class="fs-6">@(domain.Name).@(domain.SharedDomain.Name)</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
if (domain != Domains.Last())
|
|
||||||
{
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="card card-body mb-5">
|
||||||
<div class="card mb-3">
|
<div class="btn-group">
|
||||||
<div class="card-body fs-6">
|
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)">
|
||||||
<div class="row">
|
<TL>Edit</TL>
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
</a>
|
||||||
<TL>First name</TL>
|
<a class="btn btn-secondary" href="/admin/users">
|
||||||
</label>
|
<TL>Back to list</TL>
|
||||||
<div class="col-lg-8">
|
</a>
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.FirstName)</span>
|
<a class="btn btn-primary" href="/admin/support/view/@(User.Id)">
|
||||||
|
<TL>Open support</TL>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card card-xl-stretch mb-5">
|
||||||
|
<div class="card-header border-0">
|
||||||
|
<h3 class="card-title fw-bold text-dark">
|
||||||
|
<TL>Servers</TL>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-2">
|
||||||
|
@foreach (var server in Servers)
|
||||||
|
{
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<a href="/server/@(server.Uuid)" class="fs-6">@(server.Name) - @(server.Image.Name)</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Last name</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.LastName)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Email</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.Email)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Address</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.Address)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>City</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.City)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>State</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.State)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Country</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.Country)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Admin</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">
|
|
||||||
@if (User.Admin)
|
|
||||||
{
|
|
||||||
<span>✅</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>❌</span>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Status</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.Status)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Totp</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.TotpEnabled)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Discord</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(User.DiscordId)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
|
||||||
<TL>Subscription</TL>
|
|
||||||
</label>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<span class="fw-bold fs-6 text-gray-800">
|
|
||||||
|
|
||||||
</span>
|
if (server != Servers.Last())
|
||||||
|
{
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card card-xl-stretch">
|
||||||
|
<div class="card-header border-0">
|
||||||
|
<h3 class="card-title fw-bold text-dark">
|
||||||
|
<TL>Domains</TL>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-2">
|
||||||
|
@foreach (var domain in Domains)
|
||||||
|
{
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<a href="/domain/@(domain.Id)" class="fs-6">@(domain.Name).@(domain.SharedDomain.Name)</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator my-4"></div>
|
|
||||||
<div class="row">
|
if (domain != Domains.Last())
|
||||||
<label class="col-lg-4 fw-semibold text-muted">
|
{
|
||||||
<TL>Created at</TL>
|
<div class="separator my-4"></div>
|
||||||
</label>
|
}
|
||||||
<div class="col-lg-8">
|
}
|
||||||
<span class="fw-bold fs-6 text-gray-800">@(Formatter.FormatDate(User.CreatedAt))</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body fs-6">
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>First name</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.FirstName)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Last name</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.LastName)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Email</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.Email)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Address</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.Address)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>City</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.City)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>State</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.State)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Country</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.Country)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Admin</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">
|
||||||
|
@if (User.Admin)
|
||||||
|
{
|
||||||
|
<span>✅</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>❌</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Status</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.Status)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Totp</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.TotpEnabled)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Discord</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.DiscordId)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Subscription</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Created at</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(Formatter.FormatDate(User.CreatedAt))</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Register ip</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.RegisterIp)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="separator my-4"></div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-lg-4 fw-semibold text-muted">
|
||||||
|
<TL>Last ip</TL>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<span class="fw-bold fs-6 text-gray-800">@(User.LastIp)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</OnlyAdmin>
|
</OnlyAdmin>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
@using Moonlight.App.Models.Forms
|
@using Moonlight.App.Models.Forms
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Mappy.Net
|
@using Mappy.Net
|
||||||
|
@using Moonlight.App.Exceptions
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
@inject UserRepository UserRepository
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@
|
|||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<SmartForm OnValidSubmit="Save" Model="Model">
|
<SmartForm OnValidSubmit="Save" Model="Model">
|
||||||
<div class="card mb-5 mb-xl-10">
|
<div class="card mb-5 mb-xl-10">
|
||||||
<div class="card-body p-9">
|
<div class="card-body p-9 @(CurrentUser.StreamerMode ? "blur" : "")">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6 fv-row fv-plugins-icon-container">
|
<div class="col-lg-6 fv-row fv-plugins-icon-container">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -74,7 +76,7 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private UserDataModel Model = new UserDataModel();
|
private UserDataModel Model = new();
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public User CurrentUser { get; set; }
|
public User CurrentUser { get; set; }
|
||||||
@@ -89,9 +91,20 @@
|
|||||||
|
|
||||||
private Task Save()
|
private Task Save()
|
||||||
{
|
{
|
||||||
CurrentUser = Mapper.Map(CurrentUser, Model);
|
// Prevent users from locking out other users by changing their email
|
||||||
|
|
||||||
CurrentUser.Email = CurrentUser.Email.ToLower();
|
Model.Email = Model.Email.ToLower();
|
||||||
|
var userWithThatEmail = UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.Email == Model.Email);
|
||||||
|
|
||||||
|
if (userWithThatEmail != null && CurrentUser.Id != userWithThatEmail.Id)
|
||||||
|
{
|
||||||
|
Logger.Warn($"A user tried to lock another user out by changing the email. Email: {Model.Email}", "security");
|
||||||
|
throw new DisplayException("A user with that email does already exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentUser = Mapper.Map(CurrentUser, Model);
|
||||||
|
|
||||||
UserRepository.Update(CurrentUser);
|
UserRepository.Update(CurrentUser);
|
||||||
|
|
||||||
|
|||||||
@@ -2,317 +2,232 @@
|
|||||||
|
|
||||||
@using Moonlight.Shared.Components.Navigations
|
@using Moonlight.Shared.Components.Navigations
|
||||||
@using QRCoder
|
@using QRCoder
|
||||||
@using Moonlight.App.Services.Sessions
|
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using System.Text.RegularExpressions
|
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Models.Misc
|
@using Mappy.Net
|
||||||
|
@using Moonlight.App.Models.Forms
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
|
||||||
@inject TotpService TotpService
|
@inject TotpService TotpService
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
@inject IdentityService IdentityService
|
|
||||||
@inject UserService UserService
|
@inject UserService UserService
|
||||||
@inject AlertService AlertService
|
@inject NavigationManager NavigationManager
|
||||||
@inject ToastService ToastService
|
@inject ModalService ModalService
|
||||||
|
@inject Repository<User> UserRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<ProfileNavigation Index="2"/>
|
<ProfileNavigation Index="2"/>
|
||||||
|
|
||||||
<div class="card mb-5 mb-xl-10">
|
<div class="row">
|
||||||
<LazyLoader Load="Load">
|
<div class="col-12 col-md-6 p-3">
|
||||||
@if (TotpEnabled)
|
<div class="card">
|
||||||
{
|
<div class="card-header">
|
||||||
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
|
<div class="card-title">
|
||||||
<table class="w-100">
|
<TL>Two factor authentication</TL>
|
||||||
<tr>
|
</div>
|
||||||
<td rowspan="2">
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-primary">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-100">
|
|
||||||
<h4 class="text-gray-900 fw-bold ms-4">
|
|
||||||
<TL>Your account is secured with 2fa</TL>
|
|
||||||
</h4>
|
|
||||||
</td>
|
|
||||||
<td rowspan="2">
|
|
||||||
<a @onclick="Disable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
|
|
||||||
<TL>Disable</TL>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fs-6 text-gray-700 pe-7 ms-4">
|
|
||||||
<TL>anyone write a fancy text here?</TL>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="card-body fs-6">
|
||||||
else
|
<p>
|
||||||
{
|
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
|
||||||
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
|
</p>
|
||||||
<table class="w-100">
|
<div class="d-flex justify-content-center">
|
||||||
<tr>
|
@if (User.TotpEnabled)
|
||||||
<td rowspan="2">
|
{
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-primary">
|
<WButton Text="@(SmartTranslateService.Translate("Disable"))"
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
WorkingText=""
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
CssClasses="btn-danger"
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
OnClick="DisableTwoFactor">
|
||||||
</svg>
|
</WButton>
|
||||||
</span>
|
}
|
||||||
</td>
|
else
|
||||||
<td class="w-100">
|
{
|
||||||
<h4 class="text-gray-900 fw-bold ms-4">
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
<TL>Secure your account</TL>
|
WorkingText=""
|
||||||
</h4>
|
CssClasses="btn-primary"
|
||||||
</td>
|
OnClick="StartTwoFactorWizard">
|
||||||
<td rowspan="2">
|
</WButton>
|
||||||
<a @onclick="Enable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
|
}
|
||||||
<TL>Enable</TL>
|
</div>
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fs-6 text-gray-700 pe-7 ms-4">
|
|
||||||
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal fade" id="twofactorauth" tabindex="-1" style="display: none;" aria-hidden="true">
|
<div class="col-12 col-md-6 p-3">
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
<div class="card">
|
||||||
<div class="modal-content">
|
<div class="card-header">
|
||||||
<div class="modal-header flex-stack py-6">
|
<div class="card-title">
|
||||||
<h2 class="ms-3">
|
<TL>Password</TL>
|
||||||
<TL>Activate 2fa</TL>
|
</div>
|
||||||
</h2>
|
</div>
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
<div class="card-body fs-6">
|
||||||
<span class="svg-icon svg-icon-1">
|
<div class="d-flex justify-content-center">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<div class="input-group">
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
<input @bind="Password" class="form-control" type="password"/>
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
</svg>
|
WorkingText=""
|
||||||
</span>
|
CssClasses="btn-primary"
|
||||||
</div>
|
OnClick="ChangePassword">
|
||||||
</div>
|
</WButton>
|
||||||
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-dark fw-bold mb-3 mt-2">
|
|
||||||
<TL>2fa apps</TL>
|
|
||||||
</h3>
|
|
||||||
<div class="text-gray-500 fw-semibold fs-6 mb-10">
|
|
||||||
<TL>Use an app like </TL>
|
|
||||||
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>,
|
|
||||||
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>,
|
|
||||||
<a href="https://authy.com/download/" target="_blank">Authy</a>, <TL>or</TL>
|
|
||||||
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a> <TL>and scan the following QR Code</TL>
|
|
||||||
@if (EnablingTotp)
|
|
||||||
{
|
|
||||||
<div class="pt-5 text-center">
|
|
||||||
@{
|
|
||||||
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
|
||||||
|
|
||||||
var qrCodeData = qrGenerator.CreateQrCode
|
|
||||||
(
|
|
||||||
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={TotpSecret}&issuer={Uri.EscapeDataString(Issuer)}",
|
|
||||||
QRCodeGenerator.ECCLevel.Q
|
|
||||||
);
|
|
||||||
|
|
||||||
PngByteQRCode qrCode = new PngByteQRCode(qrCodeData);
|
|
||||||
byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20);
|
|
||||||
var base64 = Convert.ToBase64String(qrCodeAsPngByteArr);
|
|
||||||
}
|
|
||||||
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="notice d-flex bg-light-warning rounded border-warning border border-dashed mb-8 p-6">
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-warning me-4">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect opacity="0.3" x="2" y="2" width="20" height="20" rx="10" fill="currentColor"></rect>
|
|
||||||
<rect x="11" y="14" width="7" height="2" rx="1" transform="rotate(-90 11 14)" fill="currentColor"></rect>
|
|
||||||
<rect x="11" y="17" width="2" height="2" rx="1" transform="rotate(-90 11 17)" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div class="d-flex flex-stack flex-grow-1">
|
|
||||||
<div class="fw-semibold">
|
|
||||||
<div class="fs-6 text-gray-700">
|
|
||||||
<TL>If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:</TL>
|
|
||||||
<div class="fw-bold text-dark pt-2">@(TotpSecret)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-primary px-6 align-self-center text-nowrap float-end" data-bs-toggle="modal" data-bs-target="#test">
|
|
||||||
<TL>Next</TL>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal fade" id="test" tabindex="-1" style="display: none;" aria-hidden="true">
|
<div class="col-12 col-md-6 p-3">
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
<div class="card">
|
||||||
<div class="modal-content">
|
<div class="card-header">
|
||||||
<div class="modal-header flex-stack py-6">
|
<div class="card-title">
|
||||||
<h2 class="ms-3">
|
<TL>Preferences</TL>
|
||||||
<TL>Finish activation</TL>
|
</div>
|
||||||
</h2>
|
</div>
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
<div class="card-body fs-6">
|
||||||
<span class="svg-icon svg-icon-1">
|
<div class="form-check form-switch">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<input @bind="UserModel.StreamerMode" class="form-check-input" type="checkbox" role="switch" id="streamerModeSwitch">
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
<label class="form-check-label" for="streamerModeSwitch">
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
<TL>Streamer mode</TL>
|
||||||
</svg>
|
</label>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card-footer">
|
||||||
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
|
<div class="text-end">
|
||||||
<div class="text-gray-500 fw-semibold fs-6 mb-10">
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
<div class="alert alert-primary d-flex align-items-center p-5 mb-6">
|
WorkingText=""
|
||||||
<i class="bx bx-info-circle fs-2hx text-primary me-4">
|
CssClasses="btn-primary"
|
||||||
<span class="path1"></span><span class="path2"></span>
|
OnClick="SavePreferences">
|
||||||
</i>
|
</WButton>
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<h4 class="mb-1 text-primary">
|
|
||||||
<TL>2fa Code requiered</TL>
|
|
||||||
</h4>
|
|
||||||
<span>In order to finish the activation of 2fa, you need to enter the code your 2fa app shows you.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control form-control-lg form-control-solid mb-0" placeholder="@SmartTranslateService.Translate("2fa Code")" @bind="currentTotp"/>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<WButton CssClasses="btn btn-primary mb-2 align-self-center text-nowrap float-end" WorkingText="@SmartTranslateService.Translate("Saving")" Text="@SmartTranslateService.Translate("Finish")" OnClick="CheckAndSaveTotp">
|
|
||||||
</WButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Modals *@
|
||||||
|
<div class="modal fade" id="2fa" tabindex="-1" style="display: none" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">
|
||||||
|
<TL>Activate 2fa</TL>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body fs-6">
|
||||||
|
@if (!User.TotpEnabled)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(User.TotpSecret))
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<TL>Make sure you have installed one of the following apps on your smartphone and press continue</TL>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://authy.com/download/" target="_blank">Authy</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
<div class="separator mt-2"></div>
|
<div class="d-flex justify-content-center">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Continue"))"
|
||||||
<div class="alert alert-danger d-flex rounded ms-6 me-6 mt-6 mb-8 bg-body">
|
WorkingText="@(SmartTranslateService.Translate("Preparing"))"
|
||||||
<div class="w-100">
|
CssClasses="btn-primary"
|
||||||
<table>
|
OnClick="GenerateTwoFactorToken">
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-body">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-25">
|
|
||||||
<span class="text-gray-700 fw-semibold fs-6 ms-4 me-2">
|
|
||||||
<TL>New password</TL>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-75">
|
|
||||||
<input @bind="Password" type="password" class="form-control">
|
|
||||||
</td>
|
|
||||||
<td class="">
|
|
||||||
<WButton OnClick="ChangePassword"
|
|
||||||
CssClasses="btn-danger ms-4"
|
|
||||||
Text="@SmartTranslateService.Translate("Change")"
|
|
||||||
WorkingText="@SmartTranslateService.Translate("Changing")">
|
|
||||||
</WButton>
|
</WButton>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
}
|
||||||
</table>
|
else
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<TL>Scan the qr code and enter the code generated by the app you have scanned it in</TL>
|
||||||
|
</p>
|
||||||
|
<div class="mt-3 text-center">
|
||||||
|
@{
|
||||||
|
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||||
|
|
||||||
|
var qrCodeData = qrGenerator.CreateQrCode
|
||||||
|
(
|
||||||
|
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={User.TotpSecret}&issuer={Uri.EscapeDataString("Moonlight")}",
|
||||||
|
QRCodeGenerator.ECCLevel.Q
|
||||||
|
);
|
||||||
|
|
||||||
|
PngByteQRCode qrCode = new PngByteQRCode(qrCodeData);
|
||||||
|
byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20);
|
||||||
|
var base64 = Convert.ToBase64String(qrCodeAsPngByteArr);
|
||||||
|
}
|
||||||
|
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 d-flex justify-content-center">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text"
|
||||||
|
@bind="TwoFactorCode"
|
||||||
|
placeholder="@(SmartTranslateService.Translate("Enter your 2fa code here"))"
|
||||||
|
class="form-control"/>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Processing"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="EnableTwoFactor">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private bool TotpEnabled = false;
|
[CascadingParameter]
|
||||||
private bool EnablingTotp = false;
|
public User User { get; set; }
|
||||||
private string TotpSecret = "";
|
|
||||||
private User User;
|
|
||||||
private string Issuer = "Moonlight";
|
|
||||||
private string currentTotp = "";
|
|
||||||
|
|
||||||
|
private string TwoFactorCode = "";
|
||||||
private string Password = "";
|
private string Password = "";
|
||||||
|
private UserPreferencesDataModel UserModel;
|
||||||
|
|
||||||
private async void Enable()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
//TODO: AuditLog
|
UserModel = Mapper.Map<UserPreferencesDataModel>(User);
|
||||||
await TotpService.Enable();
|
|
||||||
TotpEnabled = await TotpService.GetEnabled();
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
EnablingTotp = true;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckAndSaveTotp()
|
private async Task StartTwoFactorWizard()
|
||||||
{
|
{
|
||||||
if (await TotpService.Verify(TotpSecret, currentTotp))
|
await ModalService.Show("2fa");
|
||||||
{
|
|
||||||
await TotpService.EnforceTotpLogin();
|
|
||||||
TotpEnabled = true;
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
await ToastService.Success("Successfully enabled 2fa!");
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await AlertService.Error("2fa code incorrect", "The given 2fa code is incorrect. Maybe check if the code in your 2fa app has changed.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Disable()
|
private async Task GenerateTwoFactorToken()
|
||||||
{
|
{
|
||||||
//TODO: AuditLog
|
await TotpService.GenerateSecret();
|
||||||
await TotpService.Disable();
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnableTwoFactor()
|
||||||
|
{
|
||||||
|
await ModalService.Hide("2fa");
|
||||||
|
await TotpService.Enable(TwoFactorCode);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Load(LazyLoader lazyLoader)
|
private async Task DisableTwoFactor()
|
||||||
{
|
{
|
||||||
await lazyLoader.SetText("Requesting secrets");
|
await TotpService.Disable();
|
||||||
|
|
||||||
TotpEnabled = await TotpService.GetEnabled();
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
|
|
||||||
await lazyLoader.SetText("Requesting identity");
|
|
||||||
User = await IdentityService.Get();
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ChangePassword()
|
private async Task ChangePassword()
|
||||||
{
|
{
|
||||||
if (Regex.IsMatch(Password, @"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z@$!%*#.,?&\d]{8,}$"))
|
await UserService.ChangePassword(User, Password);
|
||||||
{
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
await UserService.ChangePassword(User, Password);
|
}
|
||||||
|
|
||||||
//TODO: AuditLog
|
private async Task SavePreferences()
|
||||||
|
{
|
||||||
// Reload to make the user login again
|
User = Mapper.Map(User, UserModel);
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
UserRepository.Update(User);
|
||||||
}
|
await InvokeAsync(StateHasChanged);
|
||||||
else
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
{
|
|
||||||
await AlertService.Error("Error", "Your password must be at least 8 characters and must contain a number");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,12 +24,11 @@
|
|||||||
@if (Subscription == null)
|
@if (Subscription == null)
|
||||||
{
|
{
|
||||||
var config = ConfigService
|
var config = ConfigService
|
||||||
.GetSection("Moonlight")
|
.Get()
|
||||||
.GetSection("Subscriptions")
|
.Moonlight.Subscriptions.SellPass;
|
||||||
.GetSection("Sellpass");
|
|
||||||
|
|
||||||
var enableSellpass = config.GetValue<bool>("Enable");
|
var enableSellpass = config.Enable;
|
||||||
var url = config.GetValue<string>("Url");
|
var url = config.Url;
|
||||||
|
|
||||||
<h3 class="mb-2">
|
<h3 class="mb-2">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<LazyLoader Load="LoadData">
|
<LazyLoader Load="LoadData">
|
||||||
@if (CurrentServer == null)
|
@if (CurrentServer == null)
|
||||||
{
|
{
|
||||||
<NotFoundAlert />
|
<NotFoundAlert/>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -34,33 +34,16 @@
|
|||||||
{
|
{
|
||||||
if (Console.ConsoleState == ConsoleState.Connected)
|
if (Console.ConsoleState == ConsoleState.Connected)
|
||||||
{
|
{
|
||||||
if (Console.ServerState == ServerState.Installing)
|
if (Console.ServerState == ServerState.Installing || CurrentServer.Installing)
|
||||||
{
|
{
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-10">
|
<div class="fs-2hx fw-bold text-gray-800 text-center mb-5">
|
||||||
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
|
<TL>Server installation is currently running</TL>
|
||||||
<span class="me-2">
|
|
||||||
<TL>Server installation is currently running</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Terminal @ref="InstallConsole"></Terminal>
|
<div class="rounded bg-black p-3">
|
||||||
</div>
|
<Terminal @ref="InstallConsole"></Terminal>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else if (CurrentServer.Installing)
|
|
||||||
{
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="mb-10">
|
|
||||||
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
|
|
||||||
<span class="me-2">
|
|
||||||
<TL>Server installation is currently running</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Terminal @ref="InstallConsole"></Terminal>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -104,7 +87,7 @@
|
|||||||
</Route>
|
</Route>
|
||||||
<Route Path="/backups">
|
<Route Path="/backups">
|
||||||
<ServerNavigation Index="2">
|
<ServerNavigation Index="2">
|
||||||
<ServerBackups/>
|
<ServerBackups/>
|
||||||
</ServerNavigation>
|
</ServerNavigation>
|
||||||
</Route>
|
</Route>
|
||||||
<Route Path="/network">
|
<Route Path="/network">
|
||||||
|
|||||||
@@ -25,11 +25,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-4 d-flex flex-column flex-end mb-1">
|
<div class="col-4 d-flex flex-column flex-end mb-1">
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm">
|
||||||
<button class="w-100 nav-link btn btn-sm btn-success fw-bold px-4 me-1 @(Console.ServerState == ServerState.Offline ? "" : "disabled")" aria-selected="true" role="tab" @onclick="Start"><TL>Start</TL></button>
|
<button class="w-100 nav-link btn btn-sm btn-success fw-bold px-4 me-1 @(Console.ServerState == ServerState.Offline ? "" : "disabled")" aria-selected="true" role="tab" @onclick="Start">
|
||||||
<button class="w-100 nav-link btn btn-sm btn-primary fw-bold px-4 me-1 @(Console.ServerState == ServerState.Running ? "" : "disabled")" aria-selected="true" role="tab" @onclick="Restart"><TL>Restart</TL></button>
|
<TL>Start</TL>
|
||||||
|
</button>
|
||||||
|
<button class="w-100 nav-link btn btn-sm btn-primary fw-bold px-4 me-1 @(Console.ServerState == ServerState.Running ? "" : "disabled")" aria-selected="true" role="tab" @onclick="Restart">
|
||||||
|
<TL>Restart</TL>
|
||||||
|
</button>
|
||||||
@if (Console.ServerState == ServerState.Stopping)
|
@if (Console.ServerState == ServerState.Stopping)
|
||||||
{
|
{
|
||||||
<button class="w-100 nav-link btn btn-sm btn-danger fw-bold px-4 me-1" aria-selected="true" role="tab" @onclick="Kill"><TL>Kill</TL></button>
|
<button class="w-100 nav-link btn btn-sm btn-danger fw-bold px-4 me-1" aria-selected="true" role="tab" @onclick="Kill">
|
||||||
|
<TL>Kill</TL>
|
||||||
|
</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -47,7 +53,7 @@
|
|||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Shared IP</TL>:</span>
|
<span class="fw-bold"><TL>Shared IP</TL>:</span>
|
||||||
<span class="ms-1 text-muted">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
|
<span class="ms-1 text-muted @(User.StreamerMode ? "blur" : "")">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation?.Port ?? 0}")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
||||||
@@ -68,21 +74,29 @@
|
|||||||
@switch (Console.ServerState)
|
@switch (Console.ServerState)
|
||||||
{
|
{
|
||||||
case ServerState.Offline:
|
case ServerState.Offline:
|
||||||
<span class="text-danger"><TL>Offline</TL></span>
|
<span class="text-danger">
|
||||||
|
<TL>Offline</TL>
|
||||||
|
</span>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ServerState.Starting:
|
case ServerState.Starting:
|
||||||
<span class="text-warning"><TL>Starting</TL></span>
|
<span class="text-warning">
|
||||||
|
<TL>Starting</TL>
|
||||||
|
</span>
|
||||||
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.Resource.Uptime)))</span>
|
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.Resource.Uptime)))</span>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ServerState.Stopping:
|
case ServerState.Stopping:
|
||||||
<span class="text-warning"><TL>Stopping</TL></span>
|
<span class="text-warning">
|
||||||
|
<TL>Stopping</TL>
|
||||||
|
</span>
|
||||||
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.Resource.Uptime)))</span>
|
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.Resource.Uptime)))</span>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ServerState.Running:
|
case ServerState.Running:
|
||||||
<span class="text-success"><TL>Online</TL></span>
|
<span class="text-success">
|
||||||
|
<TL>Online</TL>
|
||||||
|
</span>
|
||||||
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.Resource.Uptime)))</span>
|
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.Resource.Uptime)))</span>
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -110,7 +124,9 @@
|
|||||||
<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)/" class="nav-link w-100 btn btn-flex @(Index == 0 ? "active" : "") btn-active-light-primary">
|
||||||
<i class="bx bx-terminal bx-sm me-2"></i>
|
<i class="bx bx-terminal 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"><TL>Console</TL></span>
|
<span class="fs-5">
|
||||||
|
<TL>Console</TL>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -118,7 +134,9 @@
|
|||||||
<a href="/server/@(CurrentServer.Uuid)/files" class="nav-link w-100 btn btn-flex @(Index == 1 ? "active" : "") btn-active-light-primary">
|
<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>
|
<i class="bx bx-folder 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"><TL>Files</TL></span>
|
<span class="fs-5">
|
||||||
|
<TL>Files</TL>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -126,7 +144,9 @@
|
|||||||
<a href="/server/@(CurrentServer.Uuid)/backups" class="nav-link w-100 btn btn-flex @(Index == 2 ? "active" : "") btn-active-light-primary">
|
<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>
|
<i class="bx bx-box 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"><TL>Backups</TL></span>
|
<span class="fs-5">
|
||||||
|
<TL>Backups</TL>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -134,7 +154,9 @@
|
|||||||
<a href="/server/@(CurrentServer.Uuid)/network" class="nav-link w-100 btn btn-flex @(Index == 3 ? "active" : "") btn-active-light-primary">
|
<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>
|
<i class="bx bx-wifi 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"><TL>Network</TL></span>
|
<span class="fs-5">
|
||||||
|
<TL>Network</TL>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -142,7 +164,9 @@
|
|||||||
<a href="/server/@(CurrentServer.Uuid)/addons" class="nav-link w-100 btn btn-flex @(Index == 4 ? "active" : "") btn-active-light-primary">
|
<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>
|
<i class="bx bx-plug 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"><TL>Addons</TL></span>
|
<span class="fs-5">
|
||||||
|
<TL>Addons</TL>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -150,7 +174,9 @@
|
|||||||
<a href="/server/@(CurrentServer.Uuid)/settings" class="nav-link w-100 btn btn-flex @(Index == 5 ? "active" : "") btn-active-light-primary">
|
<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>
|
<i class="bx bx-cog 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"><TL>Settings</TL></span>
|
<span class="fs-5">
|
||||||
|
<TL>Settings</TL>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -48,7 +48,9 @@
|
|||||||
<div class="card-body pt-0">
|
<div class="card-body pt-0">
|
||||||
<div class="d-flex flex-column gap-10">
|
<div class="d-flex flex-column gap-10">
|
||||||
<div class="fv-row">
|
<div class="fv-row">
|
||||||
<label class="form-label"><TL>Node</TL></label>
|
<label class="form-label">
|
||||||
|
<TL>Node</TL>
|
||||||
|
</label>
|
||||||
<div class="fw-bold fs-3">@(DeployNode.Name)</div>
|
<div class="fw-bold fs-3">@(DeployNode.Name)</div>
|
||||||
</div>
|
</div>
|
||||||
@if (Model.Image != null)
|
@if (Model.Image != null)
|
||||||
@@ -56,12 +58,16 @@
|
|||||||
var limit = Images[Model.Image];
|
var limit = Images[Model.Image];
|
||||||
|
|
||||||
<div class="fv-row">
|
<div class="fv-row">
|
||||||
<label class="form-label"><TL>Image</TL></label>
|
<label class="form-label">
|
||||||
|
<TL>Image</TL>
|
||||||
|
</label>
|
||||||
<div class="fw-bold fs-3">@(Model.Image.Name)</div>
|
<div class="fw-bold fs-3">@(Model.Image.Name)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fv-row">
|
<div class="fv-row">
|
||||||
<label class="form-label"><TL>CPU</TL></label>
|
<label class="form-label">
|
||||||
|
<TL>CPU</TL>
|
||||||
|
</label>
|
||||||
<div class="fw-bold fs-3">
|
<div class="fw-bold fs-3">
|
||||||
@{
|
@{
|
||||||
var cpu = limit.ReadValue("cpu");
|
var cpu = limit.ReadValue("cpu");
|
||||||
@@ -76,12 +82,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fv-row">
|
<div class="fv-row">
|
||||||
<label class="form-label"><TL>Memory</TL></label>
|
<label class="form-label">
|
||||||
|
<TL>Memory</TL>
|
||||||
|
</label>
|
||||||
<div class="fw-bold fs-3">@(limit.ReadValue("memory")) MB</div>
|
<div class="fw-bold fs-3">@(limit.ReadValue("memory")) MB</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fv-row">
|
<div class="fv-row">
|
||||||
<label class="form-label"><TL>Disk</TL></label>
|
<label class="form-label">
|
||||||
|
<TL>Disk</TL>
|
||||||
|
</label>
|
||||||
<div class="fw-bold fs-3">@(limit.ReadValue("disk")) MB</div>
|
<div class="fw-bold fs-3">@(limit.ReadValue("disk")) MB</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -108,15 +118,6 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (Images.Any())
|
@if (Images.Any())
|
||||||
{
|
{
|
||||||
<label class="form-label">
|
|
||||||
<TL>Image</TL>
|
|
||||||
</label>
|
|
||||||
<SmartSelect TField="Image"
|
|
||||||
@bind-Value="Model.Image"
|
|
||||||
Items="Images.Keys.ToArray()"
|
|
||||||
DisplayField="@(x => x.Name)">
|
|
||||||
</SmartSelect>
|
|
||||||
|
|
||||||
<button type="submit" class="mt-5 float-end btn btn-primary">
|
<button type="submit" class="mt-5 float-end btn btn-primary">
|
||||||
<TL>Create</TL>
|
<TL>Create</TL>
|
||||||
</button>
|
</button>
|
||||||
@@ -125,13 +126,45 @@
|
|||||||
{
|
{
|
||||||
<div class="alert alert-warning d-flex align-items-center p-5 mb-10">
|
<div class="alert alert-warning d-flex align-items-center p-5 mb-10">
|
||||||
<span>
|
<span>
|
||||||
<TL>You reached the maximum amount of servers for every image of your subscription</TL>: @(Subscription == null ? SmartTranslateService.Translate("Default") : Subscription.Name)
|
<TL>We could not find any image in your subscription you have access to</TL>: @(Subscription == null ? SmartTranslateService.Translate("Default") : Subscription.Name)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</SmartForm>
|
</SmartForm>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var keyValuePair in Images)
|
||||||
|
{
|
||||||
|
bool selected = Model.Image != null && Model.Image.Id == keyValuePair.Key.Id;
|
||||||
|
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
@if (ServerCounts[keyValuePair.Key] > keyValuePair.Value.Amount)
|
||||||
|
{
|
||||||
|
<div class="m-2 card blur">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-center">@(keyValuePair.Key.Name)</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
@(keyValuePair.Key.Description)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a href="#" class="m-2 card @(selected ? "border border-2 border-primary" : "") invisible-a" @onclick:preventDefault @onclick="() => SelectImage(keyValuePair.Key)">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-center">@(keyValuePair.Key.Name)</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
@(keyValuePair.Key.Description)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -146,6 +179,7 @@
|
|||||||
private Subscription? Subscription;
|
private Subscription? Subscription;
|
||||||
|
|
||||||
private Dictionary<Image, SubscriptionLimit> Images = new();
|
private Dictionary<Image, SubscriptionLimit> Images = new();
|
||||||
|
private Dictionary<Image, int> ServerCounts = new();
|
||||||
|
|
||||||
private ServerOrderDataModel Model = new();
|
private ServerOrderDataModel Model = new();
|
||||||
|
|
||||||
@@ -177,8 +211,8 @@
|
|||||||
.Where(x => x.Owner.Id == User.Id)
|
.Where(x => x.Owner.Id == User.Id)
|
||||||
.Count(x => x.Image.Id == image.Id);
|
.Count(x => x.Image.Id == image.Id);
|
||||||
|
|
||||||
if(serversCount < limit.Amount)
|
Images.Add(image, limit);
|
||||||
Images.Add(image, limit);
|
ServerCounts.Add(image, serversCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,7 +232,7 @@
|
|||||||
|
|
||||||
if (serversCount < limit.Amount)
|
if (serversCount < limit.Amount)
|
||||||
{
|
{
|
||||||
if(int.TryParse(limit.ReadValue("cpu"), out int cpu) &&
|
if (int.TryParse(limit.ReadValue("cpu"), out int cpu) &&
|
||||||
int.TryParse(limit.ReadValue("memory"), out int memory) &&
|
int.TryParse(limit.ReadValue("memory"), out int memory) &&
|
||||||
int.TryParse(limit.ReadValue("disk"), out int disk))
|
int.TryParse(limit.ReadValue("disk"), out int disk))
|
||||||
{
|
{
|
||||||
@@ -221,4 +255,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SelectImage(Image image)
|
||||||
|
{
|
||||||
|
Model.Image = image;
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,212 +1,187 @@
|
|||||||
@page "/servers"
|
@page "/servers"
|
||||||
@using Moonlight.App.Repositories.Servers
|
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
|
||||||
@inject ServerRepository ServerRepository
|
@inject Repository<Server> ServerRepository
|
||||||
|
@inject Repository<User> UserRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject IServiceScopeFactory ServiceScopeFactory
|
@inject IServiceScopeFactory ServiceScopeFactory
|
||||||
|
@inject IJSRuntime JsRuntime
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
@if (AllServers.Any())
|
<div class="mx-auto">
|
||||||
{
|
<div class="card card-body">
|
||||||
if (UseSortedServerView)
|
<div class="d-flex justify-content-between">
|
||||||
{
|
<span class="badge badge-primary badge-lg px-5 me-4">Beta</span>
|
||||||
var groupedServers = AllServers
|
@if (EditMode)
|
||||||
.OrderBy(x => x.Name)
|
|
||||||
.GroupBy(x => x.Image.Name);
|
|
||||||
|
|
||||||
foreach (var groupedServer in groupedServers)
|
|
||||||
{
|
|
||||||
<div class="separator separator-content my-15">@(groupedServer.Key)</div>
|
|
||||||
<div class="card card-body bg-secondary py-0 my-0 mx-0 px-0">
|
|
||||||
@foreach (var server in groupedServer)
|
|
||||||
{
|
{
|
||||||
<div class="row mx-4 my-4">
|
<div>
|
||||||
<a class="card card-body" href="/server/@(server.Uuid)">
|
<WButton Text="@(SmartTranslateService.Translate("New group"))"
|
||||||
<div class="row">
|
WorkingText=""
|
||||||
<div class="col">
|
CssClasses="btn-primary me-3"
|
||||||
<div class="d-flex align-items-center">
|
OnClick="AddGroup">
|
||||||
<div class="symbol symbol-50px me-3">
|
</WButton>
|
||||||
<i class="bx bx-md bx-server"></i>
|
<WButton Text="@(SmartTranslateService.Translate("Finish editing layout"))"
|
||||||
</div>
|
CssClasses="btn-secondary"
|
||||||
<div class="d-flex justify-content-start flex-column">
|
OnClick="async () => await SetEditMode(false)">
|
||||||
<a href="/server/@(server.Uuid)" class="text-gray-800 text-hover-primary mb-1 fs-5">
|
</WButton>
|
||||||
@(server.Name)
|
</div>
|
||||||
</a>
|
}
|
||||||
<span class="text-gray-400 fw-semibold d-block fs-6">
|
else
|
||||||
@(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) <span class="text-gray-700">- @(server.Image.Name)</span>
|
{
|
||||||
</span>
|
<WButton Text="@(SmartTranslateService.Translate("Edit layout"))"
|
||||||
</div>
|
CssClasses="btn-secondary"
|
||||||
</div>
|
OnClick="async () => await SetEditMode(true)">
|
||||||
</div>
|
</WButton>
|
||||||
<div class="d-none d-sm-block col my-auto fs-6">
|
}
|
||||||
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-sm-block col my-auto fs-6">
|
@foreach (var group in ServerGroups)
|
||||||
@if (StatusCache.ContainsKey(server))
|
{
|
||||||
|
<div class="accordion my-3" id="serverListGroup@(group.GetHashCode())">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="serverListGroup-header@(group.GetHashCode())">
|
||||||
|
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#serverListGroup-body@(group.GetHashCode())" aria-expanded="false" aria-controls="serverListGroup-body@(group.GetHashCode())">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
@if (EditMode)
|
||||||
|
{
|
||||||
|
<input @bind="group.Name" class="form-control"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(group.Name))
|
||||||
{
|
{
|
||||||
var status = StatusCache[server];
|
<TL>Unsorted servers</TL>
|
||||||
|
|
||||||
switch (status)
|
|
||||||
{
|
|
||||||
case "offline":
|
|
||||||
<span class="text-danger">
|
|
||||||
<TL>Offline</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "stopping":
|
|
||||||
<span class="text-warning">
|
|
||||||
<TL>Stopping</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "starting":
|
|
||||||
<span class="text-warning">
|
|
||||||
<TL>Starting</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "running":
|
|
||||||
<span class="text-success">
|
|
||||||
<TL>Running</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "failed":
|
|
||||||
<span class="text-gray-400">
|
|
||||||
<TL>Failed</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
<span class="text-danger">
|
|
||||||
<TL>Offline</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span class="text-gray-400">
|
<span>@(group.Name)</span>
|
||||||
<TL>Loading</TL>
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
<div>
|
||||||
</div>
|
@if (EditMode)
|
||||||
}
|
{
|
||||||
</div>
|
<WButton Text="@(SmartTranslateService.Translate("Remove group"))"
|
||||||
}
|
WorkingText=""
|
||||||
}
|
CssClasses="btn-danger"
|
||||||
else
|
OnClick="async () => await RemoveGroup(group)">
|
||||||
{
|
</WButton>
|
||||||
foreach (var server in AllServers)
|
}
|
||||||
{
|
|
||||||
<div class="row px-5 mb-5">
|
|
||||||
<a class="card card-body" href="/server/@(server.Uuid)">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="symbol symbol-50px me-3">
|
|
||||||
<i class="bx bx-md bx-server"></i>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-start flex-column">
|
|
||||||
<a href="/server/@(server.Uuid)" class="text-gray-800 text-hover-primary mb-1 fs-5">
|
|
||||||
@(server.Name)
|
|
||||||
</a>
|
|
||||||
<span class="text-gray-400 fw-semibold d-block fs-6">
|
|
||||||
@(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) <span class="text-gray-700">- @(server.Image.Name)</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
<div class="d-none d-sm-block col my-auto fs-6">
|
</h2>
|
||||||
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
<div id="serverListGroup-body@(group.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverListGroup-header@(group.GetHashCode())" data-bs-parent="#serverListGroup">
|
||||||
</div>
|
<div class="accordion-body">
|
||||||
<div class="d-none d-sm-block col my-auto fs-6">
|
<div class="row min-h-200px draggable-zone" ml-server-group="@(group.Name)">
|
||||||
@if (StatusCache.ContainsKey(server))
|
@foreach (var id in group.Servers)
|
||||||
{
|
|
||||||
var status = StatusCache[server];
|
|
||||||
|
|
||||||
switch (status)
|
|
||||||
{
|
{
|
||||||
case "offline":
|
var server = AllServers.FirstOrDefault(x => x.Id.ToString() == id);
|
||||||
<span class="text-danger">
|
|
||||||
<TL>Offline</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "stopping":
|
|
||||||
<span class="text-warning">
|
|
||||||
<TL>Stopping</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "starting":
|
|
||||||
<span class="text-warning">
|
|
||||||
<TL>Starting</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "running":
|
|
||||||
<span class="text-success">
|
|
||||||
<TL>Running</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
case "failed":
|
|
||||||
<span class="text-gray-400">
|
|
||||||
<TL>Failed</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
<span class="text-danger">
|
|
||||||
<TL>Offline</TL>
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="text-gray-400">
|
|
||||||
<TL>Loading</TL>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="alert bg-info d-flex flex-column flex-sm-row w-100 p-5">
|
|
||||||
<div class="d-flex flex-column pe-0 pe-sm-10">
|
|
||||||
<h4 class="fw-semibold">
|
|
||||||
<TL>You have no servers</TL>
|
|
||||||
</h4>
|
|
||||||
<span>
|
|
||||||
<TL>We were not able to find any servers associated with your account</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="row mt-7 px-3">
|
if (server != null)
|
||||||
<div class="card">
|
{
|
||||||
<div class="card-header">
|
<div class="col-12 col-md-3 p-3 draggable" ml-server-id="@(server.Id)">
|
||||||
<div class="card-title">
|
@if (EditMode)
|
||||||
<span class="badge badge-primary badge-lg">Beta</span>
|
{
|
||||||
</div>
|
<div class="card bg-secondary">
|
||||||
</div>
|
<div class="card-header">
|
||||||
<div class="card-body">
|
<div class="card-title">
|
||||||
<div class="row">
|
<span class="card-label">@(server.Name)</span>
|
||||||
<label class="col-lg-4 col-form-label fw-semibold fs-6">Sorted server view</label>
|
</div>
|
||||||
<div class="col-lg-8 d-flex align-items-center">
|
<div class="card-toolbar">
|
||||||
<div class="form-check form-check-solid form-switch form-check-custom fv-row">
|
<a href="#" class="btn btn-icon btn-sm btn-hover-light-primary draggable-handle">
|
||||||
<input class="form-check-input w-45px h-30px" type="checkbox" id="sortedServerView" @bind="UseSortedServerView">
|
<i class="bx bx-md bx-move"></i>
|
||||||
<label class="form-check-label" for="sortedServerView"></label>
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<TL>Hidden in edit mode</TL>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a class="invisible-a" href="/server/@(server.Uuid)">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title">
|
||||||
|
<span class="card-label">@(server.Name)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-text fs-6">
|
||||||
|
@(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) <span class="text-gray-700">- @(server.Image.Name)</span>
|
||||||
|
</span>
|
||||||
|
<div class="card-text my-1 fs-6 fw-bold @(User.StreamerMode ? "blur" : "")">
|
||||||
|
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
||||||
|
</div>
|
||||||
|
<div class="card-text fs-6">
|
||||||
|
@if (StatusCache.ContainsKey(server))
|
||||||
|
{
|
||||||
|
var status = StatusCache[server];
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case "offline":
|
||||||
|
<span class="text-danger">
|
||||||
|
<TL>Offline</TL>
|
||||||
|
</span>
|
||||||
|
break;
|
||||||
|
case "stopping":
|
||||||
|
<span class="text-warning">
|
||||||
|
<TL>Stopping</TL>
|
||||||
|
</span>
|
||||||
|
break;
|
||||||
|
case "starting":
|
||||||
|
<span class="text-warning">
|
||||||
|
<TL>Starting</TL>
|
||||||
|
</span>
|
||||||
|
break;
|
||||||
|
case "running":
|
||||||
|
<span class="text-success">
|
||||||
|
<TL>Running</TL>
|
||||||
|
</span>
|
||||||
|
break;
|
||||||
|
case "failed":
|
||||||
|
<span class="text-gray-400">
|
||||||
|
<TL>Failed</TL>
|
||||||
|
</span>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
<span class="text-danger">
|
||||||
|
<TL>Offline</TL>
|
||||||
|
</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-gray-400">
|
||||||
|
<TL>Loading</TL>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
@@ -215,11 +190,13 @@ else
|
|||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
|
|
||||||
private Server[] AllServers;
|
private Server[] AllServers;
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
private readonly Dictionary<Server, string> StatusCache = new();
|
private readonly Dictionary<Server, string> StatusCache = new();
|
||||||
|
|
||||||
private bool UseSortedServerView = false;
|
private List<ServerGroup> ServerGroups = new();
|
||||||
|
private bool EditMode = false;
|
||||||
|
|
||||||
private Task Load(LazyLoader arg)
|
private async Task Load(LazyLoader arg)
|
||||||
{
|
{
|
||||||
AllServers = ServerRepository
|
AllServers = ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -231,6 +208,23 @@ else
|
|||||||
.OrderBy(x => x.Name)
|
.OrderBy(x => x.Name)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(User.ServerListLayoutJson))
|
||||||
|
{
|
||||||
|
ServerGroups.Add(new()
|
||||||
|
{
|
||||||
|
Name = "",
|
||||||
|
Servers = AllServers.Select(x => x.Id.ToString()).ToList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ServerGroups = (JsonConvert.DeserializeObject<ServerGroup[]>(
|
||||||
|
User.ServerListLayoutJson) ?? Array.Empty<ServerGroup>()).ToList();
|
||||||
|
|
||||||
|
await CheckServerGroups();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var server in AllServers)
|
foreach (var server in AllServers)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@@ -248,6 +242,136 @@ else
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddGroup()
|
||||||
|
{
|
||||||
|
ServerGroups.Insert(0, new()
|
||||||
|
{
|
||||||
|
Name = "New group"
|
||||||
|
});
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveGroup(ServerGroup group)
|
||||||
|
{
|
||||||
|
ServerGroups.Remove(group);
|
||||||
|
|
||||||
|
await CheckServerGroups();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetEditMode(bool toggle)
|
||||||
|
{
|
||||||
|
EditMode = toggle;
|
||||||
|
|
||||||
|
if (EditMode)
|
||||||
|
{
|
||||||
|
await CheckServerGroups();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(await GetGroupsFromClient());
|
||||||
|
|
||||||
|
await CheckServerGroups();
|
||||||
|
|
||||||
|
User.ServerListLayoutJson = json;
|
||||||
|
UserRepository.Update(User);
|
||||||
|
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ServerGroup[]> GetGroupsFromClient()
|
||||||
|
{
|
||||||
|
var serverGroups = await JsRuntime.InvokeAsync<ServerGroup[]>("moonlight.serverList.getData");
|
||||||
|
|
||||||
|
// Check user data to prevent users from doing stupid stuff
|
||||||
|
foreach (var serverGroup in serverGroups)
|
||||||
|
{
|
||||||
|
if (serverGroup.Name.Length > 30)
|
||||||
|
{
|
||||||
|
Logger.Verbose("Server list group lenght too long");
|
||||||
|
return Array.Empty<ServerGroup>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task CheckServerGroups()
|
||||||
|
{
|
||||||
|
var result = new List<ServerGroup>();
|
||||||
|
|
||||||
|
// Reconstruct the data with checking for invalid server ids
|
||||||
|
foreach (var group in ServerGroups)
|
||||||
|
{
|
||||||
|
var checkedGroup = new ServerGroup()
|
||||||
|
{
|
||||||
|
Name = group.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var server in group.Servers)
|
||||||
|
{
|
||||||
|
var s = AllServers.FirstOrDefault(x => x.Id.ToString() == server);
|
||||||
|
|
||||||
|
if (s != null) // This is a check for invalid server ids
|
||||||
|
{
|
||||||
|
checkedGroup.Servers.Add(s.Id.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(checkedGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
var presentInGroup = new List<Server>();
|
||||||
|
|
||||||
|
// Copy all servers to preset in group if they are in the users servers
|
||||||
|
foreach (var group in result)
|
||||||
|
{
|
||||||
|
foreach (var id in group.Servers)
|
||||||
|
{
|
||||||
|
var s = AllServers.First(x => x.Id.ToString() == id);
|
||||||
|
presentInGroup.Add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serversMissing = new List<Server>();
|
||||||
|
|
||||||
|
// Make a list of missing servers
|
||||||
|
foreach (var server in AllServers)
|
||||||
|
{
|
||||||
|
if (presentInGroup.All(x => x.Id != server.Id))
|
||||||
|
serversMissing.Add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all missing servers into the default group
|
||||||
|
if (serversMissing.Any())
|
||||||
|
{
|
||||||
|
var defaultGroup = result.FirstOrDefault(x => x.Name == "");
|
||||||
|
|
||||||
|
if (defaultGroup == null) // If group does not exist, create it
|
||||||
|
{
|
||||||
|
defaultGroup = new ServerGroup()
|
||||||
|
{
|
||||||
|
Name = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
result.Add(defaultGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var server in serversMissing)
|
||||||
|
defaultGroup.Servers.Add(server.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerGroups = result;
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -256,8 +380,11 @@ else
|
|||||||
{
|
{
|
||||||
lock (StatusCache)
|
lock (StatusCache)
|
||||||
{
|
{
|
||||||
StatusCache.Add(server, status);
|
if (!StatusCache.ContainsKey(server))
|
||||||
InvokeAsync(StateHasChanged);
|
{
|
||||||
|
StatusCache.Add(server, status);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
{
|
|
||||||
"Moonlight": {
|
|
||||||
"AppUrl": "http://your-moonlight-url.test",
|
|
||||||
"Database": {
|
|
||||||
"Database": "database_name",
|
|
||||||
"Host": "your-moonlight-database-host.de",
|
|
||||||
"Password": "s3cr3t",
|
|
||||||
"Port": "10324",
|
|
||||||
"Username": "user_name"
|
|
||||||
},
|
|
||||||
"DiscordBotApi": {
|
|
||||||
"Enable": false,
|
|
||||||
"Token": "you api key here"
|
|
||||||
},
|
|
||||||
"DiscordBot": {
|
|
||||||
"Enable": false,
|
|
||||||
"Token": "Discord.Token.Here",
|
|
||||||
"PowerActions": false
|
|
||||||
},
|
|
||||||
"Domains": {
|
|
||||||
"_comment": "Cloudflare Api Credentials",
|
|
||||||
"AccountId": "Account Id here",
|
|
||||||
"Email": "Cloudflare Email here",
|
|
||||||
"Key": "Api Key Here"
|
|
||||||
},
|
|
||||||
"Html": {
|
|
||||||
"Headers": {
|
|
||||||
"Color": "#4b27e8",
|
|
||||||
"Description": "the next generation hosting panel",
|
|
||||||
"Keywords": "moonlight",
|
|
||||||
"Title": "Moonlight - moonlight.tld"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Marketing": {
|
|
||||||
"BrandName": "My cool project",
|
|
||||||
"Imprint": "https://mycoolproject.de/imprint",
|
|
||||||
"Privacy": "https://mycoolproject.de/privacy",
|
|
||||||
"Website": "https://mycoolproject.de/"
|
|
||||||
},
|
|
||||||
"OAuth2": {
|
|
||||||
"_exampleProviders": [
|
|
||||||
{
|
|
||||||
"Id": "discord",
|
|
||||||
"ClientId": "",
|
|
||||||
"ClientSecret": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "google",
|
|
||||||
"ClientId": "",
|
|
||||||
"ClientSecret": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Providers": [],
|
|
||||||
"EnableOverrideUrl": false,
|
|
||||||
"OverrideUrl": "http://your-moonlight-url.test"
|
|
||||||
},
|
|
||||||
"Security": {
|
|
||||||
"Token": "RANDOM UUID HERE"
|
|
||||||
},
|
|
||||||
"Mail": {
|
|
||||||
"Email": "no-reply@mycoolproject.de",
|
|
||||||
"Server": "mycoolproject.de",
|
|
||||||
"Password": "s3cr3t",
|
|
||||||
"Port": 465,
|
|
||||||
"Ssl": true
|
|
||||||
},
|
|
||||||
"Cleanup": {
|
|
||||||
"Cpu": 90,
|
|
||||||
"Memory": 8192,
|
|
||||||
"Wait": 15,
|
|
||||||
"Uptime": 6,
|
|
||||||
"Enable": false,
|
|
||||||
"MinUptime": 10
|
|
||||||
},
|
|
||||||
"Subscriptions": {
|
|
||||||
"_comment": "Not implemented",
|
|
||||||
"SellPass": {
|
|
||||||
"Enable": false,
|
|
||||||
"Url": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"DiscordNotifications": {
|
|
||||||
"Enable": false,
|
|
||||||
"WebHook": ""
|
|
||||||
},
|
|
||||||
"Statistics": {
|
|
||||||
"Enabled": true,
|
|
||||||
"Wait": 15
|
|
||||||
},
|
|
||||||
"Rating": {
|
|
||||||
"Enabled": true,
|
|
||||||
"Url": "link-to-google.page",
|
|
||||||
"MinRating": 4,
|
|
||||||
"DaysSince": 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +1,94 @@
|
|||||||
Open support;Support öffnen
|
Open support;Support Öffnen
|
||||||
About us;Über uns
|
About us;Über uns
|
||||||
Imprint;Impressum
|
Imprint;Impressum
|
||||||
Privacy;Datenschutz
|
Privacy;Privacy
|
||||||
Login;Anmelden
|
Login;Einloggen
|
||||||
Register;Registrieren
|
Register;Registrieren
|
||||||
Insert brand name...;Markenname einfügen...
|
Insert brand name...;Firmenname eingeben...
|
||||||
Save and continue;Speichern und fortfahren
|
Save and continue;Speichern und Fortfahren
|
||||||
Saving;Speichere
|
Saving;Speichern
|
||||||
Configure basics;Grundlagen konfigurieren
|
Configure basics;Grundlagen einstellen
|
||||||
Brand name;Markenname
|
Brand name;Firmenname
|
||||||
test;Test
|
test;test
|
||||||
Insert first name...;Vornamen einfügen...
|
Insert first name...;Vornamen eingeben...
|
||||||
Insert last name...;Nachname einfügen...
|
Insert last name...;Nachnamen eingeben...
|
||||||
Insert email address...;E-Mail Adresse einfügen...
|
Insert email address...;E-Mail-Adresse eingeben...
|
||||||
Add;Hinzufügen
|
Add;Hinzufügen
|
||||||
Adding...;Füge hinzu...
|
Adding...;Wird hinzugefügt...
|
||||||
Add admin accounts;Admin-Konten hinzufügen
|
Add admin accounts;Admin Konto hinzufügen
|
||||||
First name;Vorname
|
First name;Vorname
|
||||||
Last name;Nachname
|
Last name;Nachname
|
||||||
Email address;Email-Adresse
|
Email address;E-Mail-Adresse
|
||||||
Enter password;Passwort eingeben
|
Enter password;Passwort eingeben
|
||||||
Next;Weiter
|
Next;Weiter
|
||||||
Back;Zurück
|
Back;Zurück
|
||||||
Configure features;Funktionen konfigurieren
|
Configure features;Features konfigurieren
|
||||||
Support chat;Support-Chat
|
Support chat;Chat Hilfe
|
||||||
Finish;Beenden
|
Finish;Fertigstellen
|
||||||
Finalize installation;Installation abschließen
|
Finalize installation;Installation Fertigstellen
|
||||||
Moonlight basic settings successfully configured;Moonlight Grundeinstellungen erfolgreich konfiguriert
|
Moonlight basic settings successfully configured;Moonlight's Standard-Einstellungen wurden konfiguriert
|
||||||
Ooops. This page is crashed;Ooops. Diese Seite ist abgestürzt
|
Ooops. This page is crashed;Ups. Die Seite ist abgestürzt.
|
||||||
This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page;Diese Seite ist abgestürzt. Der Fehler wurde an das Moonlight-Team gemeldet. In der Zwischenzeit kannst du versuchen, die Seite neu zu laden
|
This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page;Diese Seite ist abgestürzt. Bitte versuche, sie neu zu laden.
|
||||||
Setup complete;Einrichtung abgeschlossen
|
Setup complete;Einrichtung abgeschlossen
|
||||||
It looks like this moonlight instance is ready to go;Es sieht so aus, als ob diese Moonlight-Instanz einsatzbereit ist
|
It looks like this moonlight instance is ready to go;Diese Moonlight Instanz ist bereit
|
||||||
User successfully created;Benutzer erfolgreich erstellt
|
User successfully created;Benutzer erfolgreich erstellt
|
||||||
Ooops. Your moonlight client is crashed;Ups. Dein Moonlight-Client ist abgestürzt
|
Ooops. Your moonlight client is crashed;Ups. Dein Moonlight ist abgestürzt.
|
||||||
This error has been reported to the moonlight team;Dieser Fehler wurde an das Moonlight-Team gemeldet
|
This error has been reported to the moonlight team;Dieser Fehler wurde dem Moonlight-Team mitgeteilt
|
||||||
Sign In;Anmelden
|
Sign In;Anmelden
|
||||||
Sign in to start with moonlight;Anmelden, um mit moonlight zu starten
|
Sign in to start with moonlight;Anmelden, um mit Moonlight zu starten
|
||||||
Sign in with Discord;Mit Discord anmelden
|
Sign in with Discord;Mit Discord Anmelden
|
||||||
Or with email;Oder mit E-Mail
|
Or with email;Oder mit E-Mail
|
||||||
Forgot password?;Passwort vergessen?
|
Forgot password?;Password vergessen?
|
||||||
Sign-in;Anmelden
|
Sign-in;Anmelden
|
||||||
Not registered yet?;Noch nicht registriert?
|
Not registered yet?;Noch nicht Registriert?
|
||||||
Sign up;Anmelden
|
Sign up;Registrieren
|
||||||
Authenticating;Authentifiziere
|
Authenticating;Authentifizieren...
|
||||||
Sign in with Google;Mit Google anmelden
|
Sign in with Google;Mit Google Anmelden
|
||||||
Working;Läuft
|
Working;Bitte warten...
|
||||||
Error;Fehler
|
Error;Fehler
|
||||||
Email and password combination not found;E-Mail- und Passwortkombination nicht gefunden
|
Email and password combination not found;E-Mail und Password-Kombination wurden nicht gefunden
|
||||||
Email;E-Mail
|
Email;E-mail
|
||||||
Password;Passwort
|
Password;Password
|
||||||
Account settings;Kontoeinstellungen
|
Account settings;Benutzer-Einstellungen
|
||||||
Logout;Abmelden
|
Logout;Abmelden
|
||||||
Dashboard;Dashboard
|
Dashboard;Dashboard
|
||||||
Order;Bestellung
|
Order;Bestellen
|
||||||
Website;Website
|
Website;Website
|
||||||
Database;Datenbank
|
Database;Datenbank
|
||||||
Domain;Domain
|
Domain;Domain
|
||||||
Servers;Server
|
Servers;Server
|
||||||
Websites;Webseiten
|
Websites;Websiten
|
||||||
Databases;Datenbanken
|
Databases;Datenbanken
|
||||||
Domains;Domains
|
Domains;Domains
|
||||||
Changelog;Changelog
|
Changelog;Änderungen
|
||||||
Firstname;Vorname
|
Firstname;Vorname
|
||||||
Lastname;Nachname
|
Lastname;Nachname
|
||||||
Repeat password;Passwort wiederholen
|
Repeat password;Passwort wiederholen
|
||||||
Sign Up;Anmelden
|
Sign Up;Anmelden
|
||||||
Sign up to start with moonlight;Anmelden, um mit Moonlight zu beginnen
|
Sign up to start with moonlight;Registrieren um mit Moonlight zu starten
|
||||||
Sign up with Discord;Mit Discord anmelden
|
Sign up with Discord;Mit Discord Registrieren
|
||||||
Sign up with Google;Mit Google anmelden
|
Sign up with Google;Mit Google Registrieren
|
||||||
Sign-up;Anmelden
|
Sign-up;Registrieren
|
||||||
Already registered?;Bereits registriert?
|
Already registered?;Schon Registriert?
|
||||||
Sign in;Anmelden
|
Sign in;Registrieren
|
||||||
Create something new;Etwas Neues erstellen
|
Create something new;Etwas Neues erstellen
|
||||||
Create a gameserver;Einen Gameserver erstellen
|
Create a gameserver;Einen Gameserver erstellen
|
||||||
A new gameserver in just a few minutes;Ein neuer Gameserver in nur wenigen Minuten
|
A new gameserver in just a few minutes;Ein neuer Gameserver in wenigen Minuten
|
||||||
Create a database;Erstelle eine Datenbank
|
Create a database;Eine Datenbank erstellen
|
||||||
A quick way to store your data and manage it from all around the world;Ein schneller Weg, um deine Daten zu speichern und sie von überall auf der Welt zu verwalten
|
A quick way to store your data and manage it from all around the world;Eine schnelle Möglichkeit, deine Daten von überall auf der Welt zu verwalten
|
||||||
Manage your services;Verwalte deine Services
|
Manage your services;Deine Dienste verwalten
|
||||||
Manage your gameservers;Verwalte deine Gameserver
|
Manage your gameservers;Gameserver verwalten
|
||||||
Adjust your gameservers;Deine Gameserver anpassen
|
Adjust your gameservers;Deine Gameserver anpassen
|
||||||
Manage your databases;Verwalte deine Datenbanken
|
Manage your databases;Datenbanken verwalten
|
||||||
Insert, delete and update the data in your databases;Einfügen, Löschen und Aktualisieren der Daten in deinen Datenbanken
|
Insert, delete and update the data in your databases;Daten in die Datenbank einfügen, entfernen und ändern
|
||||||
Create a website;Eine Website erstellen
|
Create a website;Eine Website erstellen
|
||||||
Make your own websites with a webspace;Eigene Websites mit einem Webspace erstellen
|
Make your own websites with a webspace;Mit einem Webspace eine Website erstellen
|
||||||
Create a domain;Eine Domain erstellen
|
Create a domain;Eine Domain erstellen
|
||||||
Make your servvices accessible throught your own domain;Mache deine Services über deine eigene Domain zugänglich
|
Make your servvices accessible throught your own domain;Mache deine Dienste mit einer Domain erreichbar
|
||||||
Manage your websites;Verwalte deine Webseiten
|
Manage your websites;Deine Websiten verwalten
|
||||||
Modify the content of your websites;Ändere den Inhalt deiner Websites
|
Modify the content of your websites;Den Inhalt deiner Websiten verwalten
|
||||||
Manage your domains;Verwalte deine Domains
|
Manage your domains;Deine Domains verwalten
|
||||||
Add, edit and delete dns records;DNS-Einträge hinzufügen, bearbeiten und löschen
|
Add, edit and delete dns records;DNS-Records hinzufügen, entfernen oder bearbeiten
|
||||||
Admin;Admin
|
Admin;Admin
|
||||||
System;System
|
System;System
|
||||||
Overview;Übersicht
|
Overview;Übersicht
|
||||||
@@ -98,71 +98,71 @@ Nodes;Nodes
|
|||||||
Images;Images
|
Images;Images
|
||||||
aaPanel;aaPanel
|
aaPanel;aaPanel
|
||||||
Users;Benutzer
|
Users;Benutzer
|
||||||
Support;Support
|
Support;Hilfe
|
||||||
Statistics;Statistiken
|
Statistics;Statistiken
|
||||||
No nodes found. Start with adding a new node;Keine Nodes gefunden. Beginne mit dem Hinzufügen einer neuen Node
|
No nodes found. Start with adding a new node;Keine Nodes gefunden. Eine neue Node hinzufügen
|
||||||
Nodename;Nodename
|
Nodename;Nodename
|
||||||
FQDN;FQDN
|
FQDN;FQDN
|
||||||
Create;Erstellen
|
Create;Erstellen
|
||||||
Creating;Erstelle
|
Creating;Wird erstellt...
|
||||||
Http port;Http-Port
|
Http port;Http Port
|
||||||
Sftp port;Sftp-Port
|
Sftp port;Sftp Port
|
||||||
Moonlight daemon port;Moonlight Daemon Port
|
Moonlight daemon port;Moonlight Daemon Port
|
||||||
SSL;SSL
|
SSL;SSL
|
||||||
CPU Usage;CPU-Auslastung
|
CPU Usage;CPU Auslastung
|
||||||
In %;In %
|
In %;In %
|
||||||
Memory;Speicher
|
Memory;Arbeitsspeicher
|
||||||
Used / Available memory;Verwendeter / Verfügbarer Speicher
|
Used / Available memory;Benutzter / Verfügbarer Arbeitsspeicher
|
||||||
Storage;Speicher
|
Storage;Speicherplatz
|
||||||
Available storage;Verfügbarer Speicher
|
Available storage;Verfügbarer Speicherplatz
|
||||||
Add a new node;Eine neue Node hinzufügen
|
Add a new node;Eine neue Node hinzufügen
|
||||||
Delete;Löschen
|
Delete;Löschen
|
||||||
Deleting;Lösche
|
Deleting;Wird gelöscht...
|
||||||
Edit;Bearbeiten
|
Edit;Bearbeiten
|
||||||
Token Id;Token Id
|
Token Id;Token ID
|
||||||
Token;Token
|
Token;Token
|
||||||
Save;Speichern
|
Save;Speichern
|
||||||
Setup;Einrichten
|
Setup;Aufsetzen
|
||||||
Open a ssh connection to your node and enter;Öffne eine SSH-Verbindung zu deiner Node und gebe
|
Open a ssh connection to your node and enter;Eine SSH verbindung zu der Node hinzufügen und öffnen
|
||||||
and paste the config below. Then press STRG+O and STRG+X to save;und füge die unten stehende Konfiguration ein. Drücke dann STRG+O und STRG+X, um zu speichern
|
and paste the config below. Then press STRG+O and STRG+X to save;und die Config darunter einfügen. Dann STRG+O und STRG+X drücken um zu Speichern
|
||||||
Before configuring this node, install the daemon;Bevor du diese Node konfigurierst, installiere den Daemon
|
Before configuring this node, install the daemon;Installiere den Daemon bevor du dieses Node konfigurierst
|
||||||
Delete this node?;Diese Node löschen?
|
Delete this node?;Diese Node löschen?
|
||||||
Do you really want to delete this node;Willst du diese Node wirklich löschen
|
Do you really want to delete this node;Möchtest du diese Node wirklich löschen?
|
||||||
Yes;Ja
|
Yes;Ja
|
||||||
No;Nein
|
No;Nein
|
||||||
Status;Status
|
Status;Status
|
||||||
Adding;Füge hinzu
|
Adding;Hinzufügen
|
||||||
Port;Port
|
Port;Port
|
||||||
Id;Id
|
Id;ID
|
||||||
Manage;Verwalten
|
Manage;Verwalten
|
||||||
Create new server;Neuen Server erstellen
|
Create new server;Neuen Server erstellen
|
||||||
No servers found;Keine Server gefunden
|
No servers found;Keine Server gefunden
|
||||||
Server name;Servername
|
Server name;Server Name
|
||||||
Cpu cores;CPU Kerne
|
Cpu cores;CPU Kerne
|
||||||
Disk;Datenträger
|
Disk;Speicherplatz
|
||||||
Image;Image
|
Image;Image
|
||||||
Override startup;Startup überschreiben
|
Override startup;Startup überschreiben
|
||||||
Docker image;Docker-Image
|
Docker image;Docker Image
|
||||||
CPU Cores (100% = 1 Core);CPU-Kerne (100% = 1 Kern)
|
CPU Cores (100% = 1 Core);CPU Kerne (100% = 1 Kern)
|
||||||
Server successfully created;Server erfolgreich erstellt
|
Server successfully created;Server erfolgreich erstellt
|
||||||
Name;Name
|
Name;Name
|
||||||
Cores;CPU Kerne
|
Cores;Kerne
|
||||||
Owner;Eigentümer
|
Owner;Besitzer
|
||||||
Value;Wert
|
Value;Wert
|
||||||
An unknown error occured;Ein unbekannter Fehler ist aufgetreten
|
An unknown error occured;Ein unbekannter Fehler ist aufgetreten
|
||||||
No allocation found;Keine Allocation gefunden
|
No allocation found;Keine Allocation gefunden
|
||||||
Identifier;Identifier
|
Identifier;Identifier
|
||||||
UuidIdentifier;UUIDIdentifier
|
UuidIdentifier;UUIDIdentifier
|
||||||
Override startup command;Startbefehl überschreiben
|
Override startup command;Startup Befehl überschreiben
|
||||||
Loading;Lade
|
Loading;Wird geladen...
|
||||||
Offline;Offline
|
Offline;Offline
|
||||||
Connecting;Verbinde
|
Connecting;Verbiden...
|
||||||
Start;Starten
|
Start;Start
|
||||||
Restart;Neustarten
|
Restart;Neustarten
|
||||||
Stop;Stoppen
|
Stop;Stoppen
|
||||||
Shared IP;Shared IP
|
Shared IP;Geteilte IP
|
||||||
Server ID;Server-ID
|
Server ID;Server ID
|
||||||
Cpu;Cpu
|
Cpu;CPU
|
||||||
Console;Konsole
|
Console;Konsole
|
||||||
Files;Dateien
|
Files;Dateien
|
||||||
Backups;Backups
|
Backups;Backups
|
||||||
@@ -171,397 +171,466 @@ Plugins;Plugins
|
|||||||
Settings;Einstellungen
|
Settings;Einstellungen
|
||||||
Enter command;Befehl eingeben
|
Enter command;Befehl eingeben
|
||||||
Execute;Ausführen
|
Execute;Ausführen
|
||||||
Checking disk space;Prüfe Speicherplatz
|
Checking disk space;Speicherplatz überprüfen
|
||||||
Updating config files;Aktualisiere Konfigurationsdateien
|
Updating config files;Konfigurations-Dateien werden geupdatet
|
||||||
Checking file permissions;Prüfe Dateiberechtigungen
|
Checking file permissions;Datei-Rechte werden überprüft
|
||||||
Downloading server image;Herunterladen des Serverabbilds
|
Downloading server image;Server Image wird heruntergeladen
|
||||||
Downloaded server image;Heruntergeladenes Serverabbild
|
Downloaded server image;Server Image wurde heruntergeladen
|
||||||
Starting;Starte
|
Starting;Startet
|
||||||
Online;Online
|
Online;Online
|
||||||
Kill;Killen
|
Kill;Kill
|
||||||
Stopping;Stoppe
|
Stopping;Stoppt
|
||||||
Search files and folders;Dateien und Verzeichnisse durchsuchen
|
Search files and folders;Ordner und Dateien durchsuchen
|
||||||
Launch WinSCP;WinSCP starten
|
Launch WinSCP;WinSCP starten
|
||||||
New folder;Neuer Ordner
|
New folder;Neuer Ordner
|
||||||
Upload;Hochladen
|
Upload;Hochladen
|
||||||
File name;Dateiname
|
File name;Dateiname
|
||||||
File size;Dateigröße
|
File size;Dateigröße
|
||||||
Last modified;Letzte Änderung
|
Last modified;Zuletzt geändert
|
||||||
Cancel;Abbrechen
|
Cancel;Abbrechen
|
||||||
Canceling;Breche ab
|
Canceling;Wird Abgebrochen
|
||||||
Running;Laufend
|
Running;Läuft
|
||||||
Loading backups;Lade Backups
|
Loading backups;Backups werden geladen
|
||||||
Started backup creation;Backup-Erstellung gestartet
|
Started backup creation;Backup wird erstellt
|
||||||
Backup is going to be created;Backup wird erstellt
|
Backup is going to be created;Backup wird erstellt werden
|
||||||
Rename;Umbenennen
|
Rename;Umbenennen
|
||||||
Move;Verschieben
|
Move;Bewegen
|
||||||
Archive;Archivieren
|
Archive;Archivieren
|
||||||
Unarchive;Unarchivieren
|
Unarchive;Archivieren rückgängig machen
|
||||||
Download;Herunterladen
|
Download;Herunterladen
|
||||||
Starting download;Starte Download
|
Starting download;Download wird gestartet
|
||||||
Backup successfully created;Backup erfolgreich erstellt
|
Backup successfully created;Backup wurde erfolgreich erstellt
|
||||||
Restore;Wiederherstellen
|
Restore;Wiederherstellen
|
||||||
Copy url;Url kopieren
|
Copy url;URL Kopieren
|
||||||
Backup deletion started;Backup-Löschung gestartet
|
Backup deletion started;Backup Löschung wird gestartet
|
||||||
Backup successfully deleted;Backup erfolgreich gelöscht
|
Backup successfully deleted;Backup wurde erfolgreich gelöscht
|
||||||
Primary;Primär
|
Primary;Primärer
|
||||||
This feature is currently not available;Diese Funktion ist derzeit nicht verfügbar
|
This feature is currently not available;Diese Funktion ist zurzeit leider nicht verfügbar
|
||||||
Send;Senden
|
Send;Senden
|
||||||
Sending;Sende
|
Sending;Wird gesendet
|
||||||
Welcome to the support chat. Ask your question here and we will help you;Willkommen im Support-Chat. Stelle hier deine Frage und wir werden dir helfen
|
Welcome to the support chat. Ask your question here and we will help you;Willkommen im Support Chat. Stelle hier deine Frage und wir helfen dir.
|
||||||
minutes ago; vor Minuten
|
minutes ago; Minuten her
|
||||||
just now;jetzt
|
just now;gerade eben
|
||||||
less than a minute ago;vor weniger als einer Minute
|
less than a minute ago;vor weniger als einer Minute
|
||||||
1 hour ago;vor 1 Stunde
|
1 hour ago;vor einer Stunde
|
||||||
1 minute ago;vor 1 Minute
|
1 minute ago;vor einer Minute
|
||||||
Failed;Fehlgeschlagen
|
Failed;Fehlgeschlagen
|
||||||
hours ago; vor Stunden
|
hours ago; Stunden her
|
||||||
Open tickets;Offene Tickets
|
Open tickets;Tickets öffnen
|
||||||
Actions;Aktionen
|
Actions;Aktionen
|
||||||
No support ticket is currently open;Kein Supportticket ist zurzeit offen
|
No support ticket is currently open;Kein Support Ticket ist zurzeit offen.
|
||||||
User information;Benutzerinformationen
|
User information;Benutzer-Information
|
||||||
Close ticket;Anfrage schließen
|
Close ticket;Ticket schließen
|
||||||
Closing;Schließe
|
Closing;Wird geschlossen...
|
||||||
The support team has been notified. Please be patient;Das Support-Team wurde benachrichtigt. Bitte habe Geduld
|
The support team has been notified. Please be patient;Das Support-Team wurde benachrichtigt. Habe etwas Geduld
|
||||||
The ticket is now closed. Type a message to open it again;Das Ticket ist jetzt geschlossen. Gib eine Nachricht ein, um es wieder zu öffnen
|
The ticket is now closed. Type a message to open it again;Das Ticket wurde geschlossen. Schreibe etwas, um es wieder zu öffnen
|
||||||
1 day ago;Vor 1 Tag
|
1 day ago;vor einem Tag
|
||||||
is typing;tippt gerade
|
is typing;schreibt...
|
||||||
are typing;tippen gerade
|
are typing;schreiben...
|
||||||
No domains available;Keine Domains verfügbar
|
No domains available;Keine Domains verfügbar
|
||||||
Shared domains;Gemeinsame Domains
|
Shared domains;Geteilte Domains
|
||||||
Shared domain;Geteilte Domains
|
Shared domain;Geteilte Domain
|
||||||
Shared domain successfully deleted;Geteilte Domain erfolgreich gelöscht
|
Shared domain successfully deleted;Geteilte Domain wurde erfolgreich gelöscht
|
||||||
Shared domain successfully added;Geteilte Domain erfolgreich hinzugefügt
|
Shared domain successfully added;Geteilte Domain wurde erfolgreich hinzugefügt
|
||||||
Domain name;Domainname
|
Domain name;Domain Name
|
||||||
DNS records for;DNS-Einträge für
|
DNS records for;DNS-Record für
|
||||||
Fetching dns records;Rufe DNS-Einträge ab
|
Fetching dns records;Es wird nach DNS-Records gesucht
|
||||||
No dns records found;Keine DNS-Einträge gefunden
|
No dns records found;Keine DNS-Records gefunden
|
||||||
Content;Inhalt
|
Content;Inhalt
|
||||||
Priority;Priorität
|
Priority;Priorität
|
||||||
Ttl;TTL
|
Ttl;TTL
|
||||||
Enable cloudflare proxy;Cloudflare Proxy einschalten
|
Enable cloudflare proxy;Cloudflare-Proxy benutzen
|
||||||
CF Proxy;CF Proxy
|
CF Proxy;CF Proxy
|
||||||
days ago; Tage vergangen
|
days ago; Tage her
|
||||||
Cancle;Abbrechen
|
Cancle;Abbrechen
|
||||||
An unexpected error occured;Ein unerwarteter Fehler ist aufgetreten
|
An unexpected error occured;Ein unbekannter Fehler ist aufgetreten
|
||||||
Testy;Testy
|
Testy;Testy
|
||||||
Error from cloudflare api;Fehler von der Cloudflare API
|
Error from cloudflare api;Fehler von der Cloudflare-API
|
||||||
Profile;Profil
|
Profile;Profil
|
||||||
No subscription available;Kein Abonnement verfügbar
|
No subscription available;Kein Abonnement verfügbar
|
||||||
Buy;Kaufen
|
Buy;Kaufen
|
||||||
Redirecting;Leite um
|
Redirecting;Weiterleiten
|
||||||
Apply;Anwenden
|
Apply;Anwenden
|
||||||
Applying code;Code anwenden
|
Applying code;Code Anwenden
|
||||||
Invalid subscription code;Ungültiger Abo-Code
|
Invalid subscription code;Unbekannter Abo-Code
|
||||||
Cancel Subscription;Abonnement kündigen
|
Cancel Subscription;Abo beenden
|
||||||
Active until;Aktiv bis
|
Active until;Aktiv bis
|
||||||
We will send you a notification upon subscription expiration;Wir senden dir eine Benachrichtigung, wenn dein Abonnement abläuft
|
We will send you a notification upon subscription expiration;Wenn dein Abonnement endet, senden wir dir eine E-Mail
|
||||||
This token has been already used;Dieser Token wurde bereits verwendet
|
This token has been already used;Dieser Token wurde schon benutzt
|
||||||
New login for;Neue Anmeldung für
|
New login for;Neue Anmeldung für
|
||||||
No records found for this day;Keine Daten für diesen Tag gefunden
|
No records found for this day;Für diesen Tag wurden keine Records gefunden
|
||||||
Change;Ändern
|
Change;Ändern
|
||||||
Changing;Ändere
|
Changing;Wird geändert
|
||||||
Minecraft version;Minecraft-Version
|
Minecraft version;Minecraft Version
|
||||||
Build version;Build-Version
|
Build version;Build Version
|
||||||
Server installation is currently running;Serverinstallation läuft derzeit
|
Server installation is currently running;Der Server wird installiert.
|
||||||
Selected;Ausgewählt
|
Selected;Ausgewählt
|
||||||
Move deleted;Verschiebe das Gelöschte
|
Move deleted;Gelöschtes Bewegen
|
||||||
Delete selected;Lösche das Ausgewählte
|
Delete selected;Ausgewähltes löschen
|
||||||
Log level;Log-Stufe
|
Log level;Log Level
|
||||||
Log message;Logmeldung
|
Log message;Log Message
|
||||||
Time;Zeit
|
Time;Zeit
|
||||||
Version;Version
|
Version;Version
|
||||||
You are running moonlight version;Du verwendest die Moonlight-Version
|
You are running moonlight version;Du benutzt die Moonlight-Version
|
||||||
Operating system;Betriebssystem
|
Operating system;Betriebssystem
|
||||||
Moonlight is running on;Moonlight läuft auf
|
Moonlight is running on;Moonlight läuft auf
|
||||||
Memory usage;Speichernutzung
|
Memory usage;Arbeitsspeicher Auslastung
|
||||||
Moonlight is using;Moonlight verwendet
|
Moonlight is using;Moonlight benutzt
|
||||||
of memory;Speicherplatz
|
of memory;des Arbeitsspeichers
|
||||||
Cpu usage;CPU Verbrauch
|
Cpu usage;CPU Auslastung
|
||||||
Refresh;Aktualisieren
|
Refresh;Neuladen
|
||||||
Send a message to all users;Eine Nachricht an alle Benutzer senden
|
Send a message to all users;Eine Nachricht an alle Benutzer senden
|
||||||
IP;IP
|
IP;IP
|
||||||
URL;URL
|
URL;URL
|
||||||
Device;Gerät
|
Device;Gerät
|
||||||
Change url;URL ändern
|
Change url;URL Ändern
|
||||||
Message;Nachricht
|
Message;Nachricht
|
||||||
Enter message;Nachricht eingeben
|
Enter message;Nachricht eingeben
|
||||||
Enter the message to send;Zu sendende Nachricht eingeben
|
Enter the message to send;Eine Nachricht zum Senden eingeben
|
||||||
Confirm;Bestätigen
|
Confirm;Bestätigen
|
||||||
Are you sure?;Bist du dir sicher?
|
Are you sure?;Bist du dir sicher?
|
||||||
Enter url;URL eingeben
|
Enter url;URL eingeben
|
||||||
An unknown error occured while starting backup deletion;Ein unbekannter Fehler ist beim Starten des Löschvorgangs des Backups aufgetreten
|
An unknown error occured while starting backup deletion;Ein unbekannter Fehler ist während der Backuplöschung aufgetreten
|
||||||
Success;Erfolg
|
Success;erfolgreich
|
||||||
Backup URL successfully copied to your clipboard;Backup-URL erfolgreich in die Zwischenablage kopiert
|
Backup URL successfully copied to your clipboard;Die Backup URL wurde in deine Zwischenablage kopiert
|
||||||
Backup restore started;Wiederherstellung des Backups gestartet
|
Backup restore started;Backup wiederherstellung gestartet
|
||||||
Backup successfully restored;Backup erfolgreich wiederhergestellt
|
Backup successfully restored;Das Backup wurde erfolgreich wiedergeherstellt
|
||||||
Register for;Registrieren für
|
Register for;Registrieren für
|
||||||
Core;Kern
|
Core;Kern
|
||||||
Logs;Logs
|
Logs;Logs
|
||||||
AuditLog;AuditLog
|
AuditLog;Audit Log
|
||||||
SecurityLog;Sicherheitslog
|
SecurityLog;Security Log
|
||||||
ErrorLog;Fehlerlog
|
ErrorLog;Error Log
|
||||||
Resources;Ressourcen
|
Resources;Resourcen
|
||||||
WinSCP cannot be launched here;WinSCP kann hier nicht gestartet werden
|
WinSCP cannot be launched here;WinSCP kann nicht gestartet werden
|
||||||
Create a new folder;Einen neuen Ordner erstellen
|
Create a new folder;Neuen Ordner erstellen
|
||||||
Enter a name;Namen eingeben
|
Enter a name;Einen Namen eingeben
|
||||||
File upload complete;Datei-Upload abgeschlossen
|
File upload complete;Dateiupload abgeschlossen
|
||||||
New server;Neuer Server
|
New server;Neuer Server
|
||||||
Sessions;Sitzungen
|
Sessions;Sitzungen
|
||||||
New user;Neuer Benutzer
|
New user;Neuer Benutzer
|
||||||
Created at;Erstellt am
|
Created at;Erstellt am
|
||||||
Mail template not found;Mailvorlage nicht gefunden
|
Mail template not found;E-Mail template wurde nicht gefunden
|
||||||
Missing admin permissions. This attempt has been logged ;Fehlende Admin-Berechtigungen. Dieser Versuch wurde geloggt
|
Missing admin permissions. This attempt has been logged ;Fehlende Admin-Rechte. Dieser Versuch wurde aufgezeichnet und ist für das ganze Admin Team sichtbar
|
||||||
Address;Adresse
|
Address;Addresse
|
||||||
City;Stadt
|
City;Stadt
|
||||||
State;Bundesland
|
State;Land
|
||||||
Country;Land
|
Country;Staat
|
||||||
Totp;Totp
|
Totp;TOTP
|
||||||
Discord;Discord
|
Discord;Discord
|
||||||
Subscription;Abonnement
|
Subscription;Abonnement
|
||||||
None;Keine
|
None;None
|
||||||
No user with this id found;Kein Benutzer mit dieser ID gefunden
|
No user with this id found;Kein Benutzer mit dieser ID gefunden
|
||||||
Back to list;Zurück zur Liste
|
Back to list;Zurück zur Liste
|
||||||
New domain;Neue Domain
|
New domain;Neue Domain
|
||||||
Reset password;Passwort zurücksetzen
|
Reset password;Password wiederherstellen
|
||||||
Password reset;Passwort zurücksetzen
|
Password reset;Password wiederherstellung
|
||||||
Reset the password of your account;Passwort für dein Konto zurücksetzen
|
Reset the password of your account;Password deines Accounts zurücksetzen
|
||||||
Wrong here?;Falsch hier?
|
Wrong here?;Falsch hier?
|
||||||
A user with this email can not be found;Ein Benutzer mit dieser E-Mail Adresse kann nicht gefunden werden
|
A user with this email can not be found;Ein Benutzer mit dieser E-Mail konnte nicht gefunden werden
|
||||||
Passwort reset successfull. Check your mail;Passwort zurücksetzen erfolgreich. Überprüfe deine Mails
|
Passwort reset successfull. Check your mail;Password wiederherstellung erfolgreich. Überprüfe dein Email Postfach
|
||||||
Discord bot;Discord-Bot
|
Discord bot;Discord Bot
|
||||||
New image;Neues Image
|
New image;Neues Image
|
||||||
Description;Beschreibung
|
Description;Beschreibung
|
||||||
Uuid;UUID
|
Uuid;UUID
|
||||||
Enter tag name;Tag-Name eingeben
|
Enter tag name;Tag Namen eingeben
|
||||||
Remove;Entfernen
|
Remove;Entfernen
|
||||||
No tags found;Keine Tags gefunden
|
No tags found;Keine Tags gefunden
|
||||||
Enter docker image name;Name des Docker-Images eingeben
|
Enter docker image name;Docker Image Namen eingeben
|
||||||
Tags;Tags
|
Tags;Tags
|
||||||
Docker images;Docker-Images
|
Docker images;Docker Images
|
||||||
Default image;Standardimage
|
Default image;Standard Image
|
||||||
Startup command;Startup-Befehl
|
Startup command;Startup Befehl
|
||||||
Install container;Container installieren
|
Install container;Install container
|
||||||
Install entry;Eintrag installieren
|
Install entry;Install entry
|
||||||
Configuration files;Konfigurationsdateien
|
Configuration files;Konfigurationsdateien
|
||||||
Startup detection;Startup-Erkennung
|
Startup detection;Startuperkennung
|
||||||
Stop command;Stopp-Befehl
|
Stop command;Stopp-Befehl
|
||||||
Successfully saved image;Image erfolgreich gespeichert
|
Successfully saved image;Das Image wurde erfolgreich gespeichert
|
||||||
No docker images found;Keine Docker Images gefunden
|
No docker images found;Keine Docker Images gefunden
|
||||||
Key;Schlüssel
|
Key;Key
|
||||||
Default value;Standardwert
|
Default value;Standardwert
|
||||||
Allocations;Allocations
|
Allocations;Zuweisung
|
||||||
No variables found;Keine Variablen gefunden
|
No variables found;Keine Variablen gefunden
|
||||||
Successfully added image;Image erfolgreich hinzugefügt
|
Successfully added image;Das Image wurde erfolgreich hinzugefügt
|
||||||
Password change for;Passwortänderung für
|
Password change for;Password ändern für
|
||||||
of;von
|
of;von
|
||||||
New node;Neue Node
|
New node;Neue Node
|
||||||
Fqdn;FQDN
|
Fqdn;FQDN
|
||||||
Cores used;Verwendete CPU Kerne
|
Cores used;Kerne genutzt
|
||||||
used;verwendet
|
used;benutzt
|
||||||
5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64
|
5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64
|
||||||
Host system information;Host-System-Informationen
|
Host system information;Host System Information
|
||||||
0;0
|
0;0
|
||||||
Docker containers running;Laufende Docker-Container
|
Docker containers running;Laufende Docker Container
|
||||||
details;Details
|
details;Details
|
||||||
1;1
|
1;1
|
||||||
2;2
|
2;2
|
||||||
DDos;DDos
|
DDos;DDos
|
||||||
No ddos attacks found;Keine DDos-Angriffe gefunden
|
No ddos attacks found;Keine DDos Attacken gefunden
|
||||||
Node;Node
|
Node;Node
|
||||||
Date;Datum
|
Date;Datum
|
||||||
DDos attack started;DDos-Angriff gestartet
|
DDos attack started;DDos Attacke gestartet
|
||||||
packets;Pakete
|
packets;Pakete
|
||||||
DDos attack stopped;DDos-Angriff gestoppt
|
DDos attack stopped;DDos Attacke gestoppt
|
||||||
packets; Pakete
|
packets; Pakete
|
||||||
Stop all;Alle stoppen
|
Stop all;Alle Stoppen
|
||||||
Kill all;Alle killen
|
Kill all;Allen Killen
|
||||||
Network in;Netzwerk ein
|
Network in;Network in
|
||||||
Network out;Netzwerk raus
|
Network out;Network out
|
||||||
Kill all servers;Alle Server killen
|
Kill all servers;Alle Server Killen
|
||||||
Do you really want to kill all running servers?;Willst du wirklich alle laufenden Server killen?
|
Do you really want to kill all running servers?;Möchtest du wirklich alle laufenden Server Killen?
|
||||||
Change power state for;Power State ändern für
|
Change power state for;Power-State ändern für
|
||||||
to;zu
|
to;zu
|
||||||
Stop all servers;Alle Server stoppen
|
Stop all servers;Alle Server stoppen
|
||||||
Do you really want to stop all running servers?;Willst du wirklich alle laufenden Server stoppen?
|
Do you really want to stop all running servers?;Möchtest du wirklich alle laufenden Server stoppen?
|
||||||
Manage ;Verwalten
|
Manage ;Verwalten
|
||||||
Manage user ;Benutzer verwalten
|
Manage user ;Benutzer verwalten
|
||||||
Reloading;Lade neu
|
Reloading;Lade neu...
|
||||||
Update;Aktualisieren
|
Update;Aktualisieren
|
||||||
Updating;Aktualisiere
|
Updating;Wird Aktualisiert...
|
||||||
Successfully updated user;Benutzer erfolgreich aktualisiert
|
Successfully updated user;Benutzer erfolgreich aktualisiert
|
||||||
Discord id;Discord ID
|
Discord id;Discord User ID
|
||||||
Discord username;Discord-Benutzername
|
Discord username;Discord Benutzername
|
||||||
Discord discriminator;Discord-Diskriminator
|
Discord discriminator;Discord Tag
|
||||||
The Name field is required.;Das Feld Name ist erforderlich
|
The Name field is required.;Der Name dieses Feldes ist erforderlich
|
||||||
An error occured while logging you in;Beim Einloggen ist ein Fehler aufgetreten
|
An error occured while logging you in;Ein Fehler ist aufgetreten, während du dich angemeldet hast
|
||||||
You need to enter an email address;Du musst eine E-Mail Adresse eingeben
|
You need to enter an email address;Du musst eine E-Mail-Adresse angeben
|
||||||
You need to enter a password;Du musst ein Passwort eingeben
|
You need to enter a password;Du musst ein Password eingeben
|
||||||
You need to enter a password with minimum 8 characters in lenght;Du musst ein Passwort mit einer länge von mindestens 8 Zeichen eingeben
|
You need to enter a password with minimum 8 characters in lenght;Du musst ein Password eingeben, das mindestens 8 Buchstaben lang ist
|
||||||
Proccessing;Bearbeite
|
Proccessing;Weid verarbeitet...
|
||||||
The FirstName field is required.;Das Feld Vorname ist erforderlich.
|
The FirstName field is required.;Das Vorname-Feld ist erforderlich
|
||||||
The LastName field is required.;Das Feld Nachname ist erforderlich.
|
The LastName field is required.;Das Nachname-Feld ist erforderlich
|
||||||
The Address field is required.;Das Adressfeld ist erforderlich.
|
The Address field is required.;Das Addresse-Feld ist erforderlich
|
||||||
The City field is required.;Das Feld Stadt ist erforderlich.
|
The City field is required.;Das Stadt-Feld ist erforderlich
|
||||||
The State field is required.;Das Feld Bundesland ist erforderlich.
|
The State field is required.;Das Staat-Feld ist erforderlich
|
||||||
The Country field is required.;Das Feld Land ist erforderlich.
|
The Country field is required.;Das Land-Feld ist erforderlich
|
||||||
Street and house number requered;Straße und Hausnummer erforderlich
|
Street and house number requered;Straße und Hausnummer erforderlich
|
||||||
Max lenght reached;Maximale Länge erreicht
|
Max lenght reached;Maximale Länge erreicht
|
||||||
Server;Server
|
Server;Server
|
||||||
stopped;gestoppt
|
stopped;gestoppt
|
||||||
Cleanups;Cleanups
|
Cleanups;Cleanups
|
||||||
executed;ausgeführt
|
executed;ausgeführt
|
||||||
Used clanup;Benutztes Cleanup
|
Used clanup;Cleanup benutzt
|
||||||
Enable;Aktivieren
|
Enable;Aktivieren
|
||||||
Disabble;Deaktivieren
|
|
||||||
Disable;Deaktivieren
|
Disable;Deaktivieren
|
||||||
Addons;Addons
|
Addons;Add-ons
|
||||||
Javascript version;Javascript Version
|
Javascript version;Javascript Version
|
||||||
Javascript file;Javascript-Datei
|
Javascript file;Javascript Datei
|
||||||
Select javascript file to execute on start;Javascript-Datei zum Ausführen beim Start auswählen
|
Select javascript file to execute on start;Javascript Datei zum Starten auswählen
|
||||||
Submit;Absenden
|
Submit;Einreichen
|
||||||
Processing;Verarbeite
|
Processing;Wird verarbeitet...
|
||||||
Go up;Nach oben
|
Go up;Nach oben gehen
|
||||||
Running cleanup;Laufende Cleanup Server
|
Running cleanup;Cleanup läuft
|
||||||
servers;Server
|
servers;Servers
|
||||||
Select folder to move the file(s) to;Ordner auswählen, in den die Datei(en) verschoben werden sollen
|
Select folder to move the file(s) to;Ordner zum Bewegen der Dateien auswählen
|
||||||
Paper version;Paperversion
|
Paper version;Paper Version
|
||||||
Join2Start;Join2Start
|
Join2Start;Join2Start
|
||||||
Server reset;Server zurücksetzen
|
Server reset;Server zurücksetzen
|
||||||
Reset;Zurücksetzen
|
Reset;Zurücksetzen
|
||||||
Resetting;Setze zurück
|
Resetting;Wird zurückgesetzt
|
||||||
Are you sure you want to reset this server?;Bist du dir sicher, dass du diesen Server zurücksetzen willst?
|
Are you sure you want to reset this server?;Möchtest du diesen Server wirklich zurücksetzen?
|
||||||
Are you sure? This cannot be undone;Bist du dir sicher? Dies kann nicht rückgängig gemacht werden
|
Are you sure? This cannot be undone;Bist du dir sicher? Dies kann nicht rückgängig gemacht werden
|
||||||
Resetting server;Setze Server zurück
|
Resetting server;Server wird zurückgesetzt...
|
||||||
Deleted file;Gelöschte Datei
|
Deleted file;Datei gelöscht
|
||||||
Reinstalling server;Installiere den Server neu
|
Reinstalling server;Server wird neuinstalliert
|
||||||
Uploading files;Lade Dateien hoch
|
Uploading files;Dateien wurden hochgeladen
|
||||||
complete;vollständig
|
complete;vollständig
|
||||||
Upload complete;Upload vollständig
|
Upload complete;Upload komplett
|
||||||
Security;Sicherheit
|
Security;Sicherheit
|
||||||
Subscriptions;Abonnements
|
Subscriptions;Abonnements
|
||||||
2fa Code;2FA Code
|
2fa Code;2FA Code
|
||||||
Your account is secured with 2fa;Dein Konto ist mit 2FA gesichert
|
Your account is secured with 2fa;Dein Account wird mit 2-FA gesichert
|
||||||
anyone write a fancy text here?;Kann hier jemand einen netten Text schreiben? Ja sicher: Ich war hier. - Dannyx
|
anyone write a fancy text here?;hier einen schönen Text schreiben? -Nö.
|
||||||
Activate 2fa;2FA aktivieren
|
Activate 2fa;2-FA Aktivieren
|
||||||
2fa apps;2FA Apps
|
2fa apps;2-FA Apps
|
||||||
Use an app like ;Benutze eine App wie
|
Use an app like ;Benutze eine App wie
|
||||||
or;oder
|
or;oder
|
||||||
and scan the following QR Code;und scanne den folgenden QR-Code
|
and scan the following QR Code;und scanne diesen QR-Code
|
||||||
If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:;Wenn du Probleme bei der Verwendung des QR-Codes hast, wähle in der App manuelle Eingabe und gib deine E-Mail-Adresse und den folgenden Code ein:
|
If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:;Wenn du Probleme mit dem Scannen des Qr-Codes has, benutze doch die Manuelle Eingabe der App und gib deine E-Mail und den folgenden Code ein:
|
||||||
Finish activation;Aktivierung beenden
|
Finish activation;Aktivierung fertig
|
||||||
2fa Code requiered;2FA-Code erforderlich
|
2fa Code requiered;2-FA Code erforderlich
|
||||||
New password;Neues Passwort
|
New password;Neues Password
|
||||||
Secure your account;Sichere dein Konto
|
Secure your account;Deinen Account sichern
|
||||||
2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.;2FA fügt eine weitere Sicherheitsebene zu deinem Konto hinzu. Du musst dann noch einen 6-stelligen Code eingeben, um dich anzumelden.
|
2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.;2-FA fügt eine weitere Sicherheits-Schicht hinzu. Du musst einen 6-Ziffern-Code eingeben, um dich anzumelden.
|
||||||
New subscription;Neues Abonnement
|
New subscription;Neues Abonnement
|
||||||
You need to enter a name;Du musst einen Namen eingeben
|
You need to enter a name;Du musst einen Namen eingeben
|
||||||
You need to enter a description;Du musst eine Beschreibung eingeben
|
You need to enter a description;Du musst eine Beschreibung eigeben
|
||||||
Add new limit;Neues Limit hinzufügen
|
Add new limit;Ein neues Limit hinzufügen
|
||||||
Create subscription;Abonnement erstellen
|
Create subscription;Abonnement erstellen
|
||||||
Options;Optionen
|
Options;Optionen
|
||||||
Amount;Betrag
|
Amount;Betrag
|
||||||
Do you really want to delete it?;Willst du es wirklich löschen?
|
Do you really want to delete it?;Möchtes du es wirklich löschen?
|
||||||
Loading your subscription;Abonnement laden
|
Loading your subscription;Dein Abonnement wird geladen
|
||||||
Searching for deploy node;Suche nach einer Deploy Node
|
Searching for deploy node;Suche nach einer verfügbaren Node
|
||||||
Searching for available images;Suche nach verfügbaren Images
|
Searching for available images;Nach verfügbaren Images wird gesucht
|
||||||
Server details;Server-Details
|
Server details;Server Details
|
||||||
Configure your server;Konfiguriere deinen Server
|
Configure your server;Deinen Server konfigurieren
|
||||||
Default;Standard
|
Default;Standart
|
||||||
You reached the maximum amount of servers for every image of your subscription;Du hast die maximale Anzahl von Servern für jedes Image deines Abonnements erreicht
|
You reached the maximum amount of servers for every image of your subscription;Du hast die maximale Anzahl an Images von deinem Abonnement erreicht.
|
||||||
Personal information;Persönliche Informationen
|
Personal information;Persönliche Informationen
|
||||||
Enter code;Code eingeben
|
Enter code;Code eingeben
|
||||||
Server rename;Server umbenennen
|
Server rename;Server Umbenennen
|
||||||
Create code;Code erstellen
|
Create code;Code erstellen
|
||||||
Save subscription;Abonnement speichern
|
Save subscription;Abonnement speichern
|
||||||
Enter your information;Gib deine Informationen ein
|
Enter your information;Informationen eingeben
|
||||||
You need to enter your full name in order to use moonlight;Du musst deinen vollständigen Namen eingeben, um Moonlight zu verwenden
|
You need to enter your full name in order to use moonlight;Du musst deinen ganzen Namen eingeben, um Moonlight zu nutzen
|
||||||
No node found;Keine Node gefunden
|
No node found;Kein Node gefunden
|
||||||
No node found to deploy to found;Kein Node zum Deployen gefunden
|
No node found to deploy to found;Keine Node für die Bereitstellung gefunden
|
||||||
Node offline;Node offline
|
Node offline;Node offline
|
||||||
The node the server is running on is currently offline;Die Node, auf der der Server läuft, ist derzeit offline
|
The node the server is running on is currently offline;Das Node, auf dem der Server grade läuft, ist offline
|
||||||
Server not found;Server nicht gefunden
|
Server not found;Server konnte nicht gefunden werden
|
||||||
A server with that id cannot be found or you have no access for this server;Ein Server mit dieser ID kann nicht gefunden werden oder du hast keinen Zugriff auf diesen Server
|
A server with that id cannot be found or you have no access for this server;Ein Server mit dieser ID konnte nicht gefunden werden oder du hast keinen Zugriff auf ihn
|
||||||
Compress;Komprimieren
|
Compress;Komprimieren
|
||||||
Decompress;Dekomprimieren
|
Decompress;De-Komprimieren
|
||||||
Moving;Verschieben
|
Moving;Bewegen...
|
||||||
Compressing;Komprimieren
|
Compressing;Komprimieren...
|
||||||
selected;ausgewählt
|
selected;Ausgewählt
|
||||||
New website;Neue Website
|
New website;Neue Website
|
||||||
Plesk servers;Plesk-Server
|
Plesk servers;Plesk Servers
|
||||||
Base domain;Basisdomain
|
Base domain;Base Domain
|
||||||
Plesk server;Plesk-Server
|
Plesk server;Plesk Server
|
||||||
Ftp;FTP
|
Ftp;FTP
|
||||||
No SSL certificate found;Kein SSL-Zertifikat gefunden
|
No SSL certificate found;Keine SSL-Zertifikate gefunden
|
||||||
Ftp Host;FTP Host
|
Ftp Host;FTP Host
|
||||||
Ftp Port;FTP Port
|
Ftp Port;FTP Port
|
||||||
Ftp Username;FTP Benutzername
|
Ftp Username;FTP Username
|
||||||
Ftp Password;FTP Passwort
|
Ftp Password;FTP Passwort
|
||||||
Use;Verwenden
|
Use;Benutzen
|
||||||
SSL Certificates;SSL-Zertifikate
|
SSL Certificates;SSL Zertifikate
|
||||||
SSL certificates;SSL-Zertifikate
|
SSL certificates;SSL Zertifikate
|
||||||
Issue certificate;Zertifikat ausstellen lassen
|
Issue certificate;SSL-Zertifikat erstellen lassen
|
||||||
New plesk server;Neuer Plesk-Server
|
New plesk server;Neuer Plesk Server
|
||||||
Api url;API-URL
|
Api url;API URL
|
||||||
Host system offline;Hostsystem offline
|
Host system offline;Host System Offline
|
||||||
The host system the website is running on is currently offline;Das Hostsystem, auf dem die Website läuft, ist derzeit offline
|
The host system the website is running on is currently offline;Das Host System, auf dem diese Website läuft, ist offline
|
||||||
No SSL certificates found;Keine SSL-Zertifikate gefunden
|
No SSL certificates found;Keine SSL-Zertifikate gefunden
|
||||||
No databases found for this website;Keine Datenbanken für diese Website gefunden
|
No databases found for this website;Dieser Website konnten keine Datenbanken zugeordnet werden
|
||||||
The name should be at least 8 characters long;Der Name muss mindestens 8 Zeichen lang sein
|
The name should be at least 8 characters long;Der Name sollte mindestens 8 Zeichen lang sein
|
||||||
The name should only contain of lower case characters and numbers;Der Name darf nur aus Kleinbuchstaben und Zahlen bestehen
|
The name should only contain of lower case characters and numbers;Der Name sollte nur Kleinbuchstaben und Zahlen enthalten
|
||||||
Error from plesk;Fehler von Plesk
|
Error from plesk;Error von Plesk
|
||||||
Host;Host
|
Host;Host
|
||||||
Username;Benutzername
|
Username;Benutzername
|
||||||
SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one;SRV-Einträge können aufgrund des Cloudflare-Api-Clients nicht aktualisiert werden. Bitte lösche den Eintrag und erstelle einen neuen
|
SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one;SRV Records können aufgrund von Cloudflare nicht geupdatet werden. Bitte lösche den Record und erstelle einen neuen.
|
||||||
The User field is required.;Das Feld Benutzer ist erforderlich
|
The User field is required.;Das Benutzer-Feld ist erforderlich
|
||||||
You need to specify a owner;Du musst einen Besitzer angeben
|
You need to specify a owner;Du musst einen Server-Besitzer angeben
|
||||||
You need to specify a image;Du musst ein Image angeben
|
You need to specify a image;Du musst ein Image angeben
|
||||||
Api Url;API URL
|
Api Url;API URL
|
||||||
Api Key;API Key
|
Api Key;Api Key
|
||||||
Duration;Dauer
|
Duration;Dauer
|
||||||
Enter duration of subscription;Dauer des Abonnements eingeben
|
Enter duration of subscription;Dauer des Abonnements eingeben
|
||||||
Copied code to clipboard;Code in die Zwischenablage kopiert
|
Copied code to clipboard;Code in die Zwischenablage kopiert
|
||||||
Invalid or expired subscription code;Ungültiger oder abgelaufener Abo-Code
|
Invalid or expired subscription code;Ungültiger oder Abgelaufener Abo-Code
|
||||||
Current subscription;Aktuelles Abonnement
|
Current subscription;Dein Abonnement
|
||||||
You need to specify a server image;Du musst ein Server-Image angeben
|
You need to specify a server image;Du musst ein Image angeben
|
||||||
CPU;CPU
|
CPU;CPU
|
||||||
Hour;Stunde
|
Hour;Stunde
|
||||||
Day;Tag
|
Day;Tag
|
||||||
Month;Monat
|
Month;Monat
|
||||||
Year;Jahr
|
Year;Jahr
|
||||||
All time;Seit Beginn
|
All time;Für immer
|
||||||
This function is not implemented;Diese Funktion ist nicht eingebaut
|
This function is not implemented;Diese Funktion wurde noch nicht hinzugefügt
|
||||||
Domain details;Domaindetails
|
Domain details;Domain Details
|
||||||
Configure your domain;Konfiguriere deine Domain
|
Configure your domain;Deine Domain konfigurieren
|
||||||
You reached the maximum amount of domains in your subscription;Du hast die maximale Anzahl an Domains in deinem Abonnement erreicht
|
You reached the maximum amount of domains in your subscription;Du hast das Maximum an Domains in deinem Abonnement erreicht
|
||||||
You need to specify a shared domain;Du musst eine gemeinsame Domain angeben
|
You need to specify a shared domain;Du musst eine Shared-Domain angeben
|
||||||
A domain with this name does already exist for this shared domain;Eine Domain mit diesem Namen existiert bereits für diese gemeinsame Domain
|
A domain with this name does already exist for this shared domain;Eine Domain mit diesem Name existiert bereits in dieser Shared-Domain
|
||||||
The Email field is required.;Das Feld E-Mail ist erforderlich.
|
The Email field is required.;Das E-Mail-Feld ist erforderlich
|
||||||
The Password field is required.;Das Feld Passwort ist erforderlich.
|
The Password field is required.;Das Password-Feld ist erforderlich
|
||||||
The ConfirmPassword field is required.;Das Feld Passwort bestätigen ist erforderlich.
|
The ConfirmPassword field is required.;Das Password-Bestätigen-Feld ist erforderlich
|
||||||
Passwords need to match;Passwörter müssen übereinstimmen
|
Passwords need to match;Die Passwörter müssen übereinstimmen
|
||||||
Cleanup exception;Cleanup Ausnahme
|
Cleanup exception;Cleanup Ausnahme
|
||||||
No shared domain found;Keine gemeinsame Domain gefunden
|
No shared domain found;Keine Shared-Domain gefunden
|
||||||
Searching for deploy plesk server;Suche nach einem Deploy Plesk Server
|
Searching for deploy plesk server;Suchen um den Plesk Server aufzusetzen
|
||||||
No plesk server found;Kein Plesk Server gefunden
|
No plesk server found;Kein Plesk Server gefunden
|
||||||
No plesk server found to deploy to;Kein Plesk Server für die Bereitstellung gefunden
|
No plesk server found to deploy to;Keinen Plesk Server zum Aufsetzen gefunden
|
||||||
No node found to deploy to;Keine Node für die Bereitstellung gefunden
|
No node found to deploy to;Kein Node zum Aufsetzen
|
||||||
Website details;Webseiten-Details
|
Website details;Website Details
|
||||||
Configure your website;Konfiguriere deine Website
|
Configure your website;Konfiguriere deine Website
|
||||||
The name cannot be longer that 32 characters;Der Name darf nicht länger als 32 Zeichen sein
|
The name cannot be longer that 32 characters;Der Name kann nicht länger als 32 Zeichen sein
|
||||||
The name should only consist of lower case characters;Der Name darf nur aus Kleinbuchstaben bestehen
|
The name should only consist of lower case characters;Der Name sollte nur aus Kleinbuchstaben bestehen
|
||||||
News;Neuigkeiten
|
News;Neuigkeiten
|
||||||
Title...;Titel...
|
Title...;Titel...
|
||||||
Enter text...;Text eingeben...
|
Enter text...;Text einfügen...
|
||||||
Saving...;Speichere...
|
Saving...;Wird gespeichert...
|
||||||
Deleting...;Lösche...
|
Deleting...;Wird gelöscht...
|
||||||
Delete post;Beitrag löschen
|
Delete post;Post löschen
|
||||||
Do you really want to delete the post ";Willst du den Beitrag wirklich löschen "
|
Do you really want to delete the post ";Post löschen? "
|
||||||
You have no domains;Du hast keine Domains
|
You have no domains;Du hast keine Domains
|
||||||
We were not able to find any domains associated with your account;Wir konnten keine Domains finden, die mit deinem Konto verbunden sind
|
We were not able to find any domains associated with your account;Wir haben keine Domains, die mit deinem Account verbunden sind, gefunden
|
||||||
You have no websites;Du hast keine Websites
|
You have no websites;Du hast keine Websites
|
||||||
We were not able to find any websites associated with your account;Wir konnten keine Webseiten finden, die mit deinem Konto verbunden sind
|
We were not able to find any websites associated with your account;Wir haben keine Websiten, die mit deinem Account verbunden sind, gefunden
|
||||||
Guest;Gast
|
Guest;Gast
|
||||||
You need a domain;Du brauchst eine Domain
|
You need a domain;Du brauchst eine Domain
|
||||||
New post;Neuer Post
|
New post;Neuer Post
|
||||||
New entry;Neuer Eintrag
|
New entry;Neuer Eintrag
|
||||||
|
You have no servers;Du hast keine Server
|
||||||
|
We were not able to find any servers associated with your account;Wir haben keine Server, die mit deinem Account verbunden sind, gefunden
|
||||||
|
Error creating server on wings;Fehler bei der Erstellung des Servers auf Wings
|
||||||
|
An unknown error occured while restoring a backup;Ein unbekannter Fehler ist während der Backup-Wiederherstellung aufgetreten
|
||||||
|
Error from daemon;Fehler vom Daemon
|
||||||
|
End;Ende
|
||||||
|
Cloud panel;Cloud Panel
|
||||||
|
Cloud panels;Cloud Panels
|
||||||
|
New cloud panel;Neues Cloud Panel
|
||||||
|
You need to enter an api key;Du musst einen API-Key eigeben
|
||||||
|
Webspaces;Webspaces
|
||||||
|
New webspace;Neuer Webspace
|
||||||
|
The uploaded file should not be bigger than 100MB;Die Datei sollte nicht größer als 100MB sein
|
||||||
|
An unknown error occured while uploading a file;Ein unbekannter Fehler ist während dem Datei Upload aufgetreten
|
||||||
|
No databases found for this webspace;Keine Datenbanken für diesen Webspace gefunden
|
||||||
|
Sftp;SFTP
|
||||||
|
Sftp Host;SFTP Host
|
||||||
|
Sftp Port;SFTP Port
|
||||||
|
Sftp Username;SFTP Benutzername
|
||||||
|
Sftp Password;SFTP Password
|
||||||
|
Lets Encrypt certificate successfully issued;Lets Encrypt Zertifikat erfolgreich erstellt
|
||||||
|
Add shared domain;Shared Domain Hinzufügen
|
||||||
|
Webspace;Webspace
|
||||||
|
You reached the maximum amount of websites in your subscription;Du hast das Maximum an Websiten in deinem Abonnement erreicht
|
||||||
|
Searching for deploy web host;Suchen um den Webhost aufzusetzen
|
||||||
|
Webspace details;Webspace Details
|
||||||
|
Web host;Web host
|
||||||
|
Configure your webspaces;Konfiguriere deine Webspaces
|
||||||
|
You reached the maximum amount of webspaces in your subscription;Du hast das Maximum an Webspaces in deinem Abonnement erreicht
|
||||||
|
Create a webspace;Einen Webspace erstellen
|
||||||
|
Manage your webspaces;Deine Webspaces verwalten
|
||||||
|
Modify the content of your webspaces;Den Inhalt deiner Webspaces verwalten
|
||||||
|
Successfully updated password;Password erfolgreich geupdatet
|
||||||
|
An unknown error occured while sending your message;Ein unbekannter Fehler ist während dem Senden von deiner Nachricht aufgetreten
|
||||||
|
Open chats;Offene Chats
|
||||||
|
No message sent yet;Keine Nachrichten gesendet
|
||||||
|
Support ticket open;Support-Ticket geöffnet
|
||||||
|
Support ticket closed;Support-Ticket geschlossen
|
||||||
|
Your connection has been paused;Deine Verbindung wurde pausiert
|
||||||
|
We paused your connection because of inactivity. The resume just focus the tab and wait a few seconds;Wir haben deine Verbindung aufgrund von inaktivität pausiert. Wechsle auf den Tab und warte ein paar Sekunden
|
||||||
|
Failed to reconnect to the moonlight servers;Die Wiederverbindung zu den Moonlight-Servern ist gescheitert
|
||||||
|
We were unable to reconnect to moonlight. Please refresh the page;Verbindung zu Moonlight fehlgeschlagen. Bitte aktualisiere die Seite
|
||||||
|
Failed to reconnect to the moonlight servers. The connection has been rejected;Die Wiederverbindung zu den Moonlight-Servern ist fehlgeschlagen. Die Verbindung wurde abgelehnt
|
||||||
|
We were unable to reconnect to moonlight. Most of the time this is caused by an update of moonlight. Please refresh the page;Verbindung zu Moonlight fehlgeschlagen. Meistens wird dies durch eine Aktualisierung von Moonlight verursacht. Bitte aktualisiere die Seite
|
||||||
|
Verifying token, loading user data;Token verifizieren, lade Benutzerdaten
|
||||||
|
Reload config;Konfiguration neuladen
|
||||||
|
Successfully reloading configuration;Konfiguration wird neugeladen...
|
||||||
|
Successfully reloaded configuration;Die Konfiguration wurde erfolgreich neugeladen
|
||||||
|
Flows;Flows
|
||||||
|
Add node;Node Hinzufügen
|
||||||
|
Web system;Web System
|
||||||
|
Servers with this image;Server mit diesem Image
|
||||||
|
You need to specify a user;Du musst einen Benutzer angeben
|
||||||
|
Import;Importieren
|
||||||
|
Export;Exportieren
|
||||||
|
Exporting;Wird exportiert
|
||||||
|
Successfully imported image;Das Image wurde erfolgreich importiert.
|
||||||
|
Forge version;Forge Version
|
||||||
|
Fabric version;Fabric Version
|
||||||
|
Fabric loader version;Fabric Loader Version
|
||||||
|
Rate;Rate
|
||||||
|
Hey, can i borrow you for a second?;Hey, kann ich dich mal kurz ausleihen?
|
||||||
|
We want to improve our services and get a little bit of feedback how we are currently doing. Please leave us a rating;Da wir unsere Dienste ständig verbessern, möchten wir dich um Feedback bitten. Bitte Bewerte uns:
|
||||||
|
Thanks for your rating;Danke für deine Bewertung
|
||||||
|
It would be really kind of you rating us on a external platform as it will help our project very much;Es wäre wirklich nett, wenn du uns auf einer externen Plattform bewerten würdest, denn das würde unserem Projekt sehr helfen
|
||||||
|
Close;Schließen
|
||||||
|
Rating saved;Bewertung gespeichert
|
||||||
|
Group;Gruppe
|
||||||
|
Beta;Beta
|
||||||
|
Create a new group;Eine neue Gruppe erstellen
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user