Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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");
|
||||||
|
|||||||
320
Moonlight/App/Configuration/ConfigV1.cs
Normal file
320
Moonlight/App/Configuration/ConfigV1.cs
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
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("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 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");
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IConfigurationSection> GetChildren()
|
Configuration = JsonConvert.DeserializeObject<ConfigV1>(
|
||||||
{
|
File.ReadAllText(path)
|
||||||
return Configuration.GetChildren();
|
) ?? new ConfigV1();
|
||||||
|
|
||||||
|
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IChangeToken GetReloadToken()
|
public void Save(ConfigV1 configV1)
|
||||||
{
|
{
|
||||||
return Configuration.GetReloadToken();
|
Configuration = configV1;
|
||||||
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationSection GetSection(string key)
|
public void Save()
|
||||||
{
|
{
|
||||||
return Configuration.GetSection(key);
|
var path = PathBuilder.File("storage", "configs", "config.json");
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string this[string key]
|
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
|
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigV1 Get()
|
||||||
{
|
{
|
||||||
get => Configuration[key];
|
return Configuration;
|
||||||
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,6 +13,7 @@ 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;
|
||||||
|
|
||||||
@@ -31,15 +33,15 @@ public class DomainService
|
|||||||
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
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -93,9 +95,36 @@ public class DomainService
|
|||||||
{
|
{
|
||||||
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}";
|
||||||
@@ -145,7 +174,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(" ");
|
||||||
|
|
||||||
|
|||||||
@@ -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!;
|
||||||
|
|
||||||
|
await new Retry()
|
||||||
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
|
{
|
||||||
|
result = await WingsApiHelper.Get<ServerDetails>(
|
||||||
server.Node,
|
server.Node,
|
||||||
$"api/servers/{server.Uuid}"
|
$"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()
|
||||||
|
.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);
|
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)
|
||||||
@@ -303,12 +323,18 @@ public class ServerService
|
|||||||
var newServerData = ServerRepository.Add(server);
|
var newServerData = ServerRepository.Add(server);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
await new Retry()
|
||||||
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
{
|
{
|
||||||
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
||||||
{
|
{
|
||||||
Uuid = newServerData.Uuid,
|
Uuid = newServerData.Uuid,
|
||||||
StartOnCompletion = false
|
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);
|
||||||
|
|
||||||
|
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);
|
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)
|
||||||
|
|||||||
56
Moonlight/App/Services/Sessions/SessionClientService.cs
Normal file
56
Moonlight/App/Services/Sessions/SessionClientService.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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 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();
|
||||||
|
|
||||||
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -38,9 +38,8 @@ public class UserService
|
|||||||
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)
|
||||||
@@ -73,7 +72,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 => {});
|
||||||
|
|||||||
@@ -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,14 +42,53 @@ 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)
|
||||||
|
{
|
||||||
|
if (shouldUseSentry)
|
||||||
{
|
{
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.Verbose()
|
.MinimumLevel.Verbose()
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.WriteTo.Console(
|
.WriteTo.Console(
|
||||||
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
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
|
||||||
|
{
|
||||||
|
if (shouldUseSentry)
|
||||||
|
{
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Information()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.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();
|
.CreateLogger();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -57,8 +98,10 @@ namespace Moonlight
|
|||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.WriteTo.Console(
|
.WriteTo.Console(
|
||||||
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
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();
|
.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,35 +59,44 @@ 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">
|
|
||||||
<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"/>
|
|
||||||
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<TL>Launch WinSCP</TL>
|
<TL>Launch WinSCP</TL>
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<span class="visually-hidden"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<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>
|
||||||
|
<button @onclick="CreateFile" class="dropdown-item btn">
|
||||||
<TL>New file</TL>
|
<TL>New file</TL>
|
||||||
</button>
|
</button>
|
||||||
|
</li>
|
||||||
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
|
<li>
|
||||||
<span class="svg-icon svg-icon-2">
|
<button @onclick="CreateFolder" class="dropdown-item btn">
|
||||||
<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>
|
<TL>New folder</TL>
|
||||||
</button>
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
|
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
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">
|
||||||
|
<div class="@(User.StreamerMode ? "blur" : "")">
|
||||||
@(User.FirstName) @(User.LastName)
|
@(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; }
|
|
||||||
}
|
|
||||||
@@ -38,9 +38,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async void Dispose()
|
public async void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await Xterm.DisposeAsync();
|
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/>
|
||||||
|
|||||||
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.IdentityService.GetIp())
|
||||||
<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.IdentityService.GetDevice())
|
||||||
|
</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 () =>
|
||||||
{
|
{
|
||||||
@@ -114,30 +123,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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,7 +161,9 @@
|
|||||||
|
|
||||||
if (b)
|
if (b)
|
||||||
{
|
{
|
||||||
foreach (var session in SessionService.GetAll())
|
foreach (var session in AllSessions!)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -167,12 +173,18 @@
|
|||||||
{
|
{
|
||||||
Logger.Warn("Error sending user a alert");
|
Logger.Warn("Error sending user a alert");
|
||||||
Logger.Warn(e);
|
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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,15 @@ else
|
|||||||
</div>
|
</div>
|
||||||
<div class="card card-body mb-5">
|
<div class="card card-body mb-5">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)"><TL>Edit</TL></a>
|
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)">
|
||||||
<a class="btn btn-secondary" href="/admin/users"><TL>Back to list</TL></a>
|
<TL>Edit</TL>
|
||||||
<a class="btn btn-primary" href="/admin/support/view/@(User.Id)"><TL>Open support</TL></a>
|
</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>
|
</div>
|
||||||
<div class="card card-xl-stretch mb-5">
|
<div class="card card-xl-stretch mb-5">
|
||||||
@@ -209,6 +215,24 @@ else
|
|||||||
<span class="fw-bold fs-6 text-gray-800">@(Formatter.FormatDate(User.CreatedAt))</span>
|
<span class="fw-bold fs-6 text-gray-800">@(Formatter.FormatDate(User.CreatedAt))</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|||||||
@@ -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,130 +2,152 @@
|
|||||||
|
|
||||||
@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>
|
|
||||||
<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>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="card-body fs-6">
|
||||||
</table>
|
<p>
|
||||||
|
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
|
||||||
|
</p>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
@if (User.TotpEnabled)
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Disable"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="DisableTwoFactor">
|
||||||
|
</WButton>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="StartTwoFactorWizard">
|
||||||
|
</WButton>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 p-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title">
|
||||||
|
<TL>Password</TL>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body fs-6">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="input-group">
|
||||||
|
<input @bind="Password" class="form-control" type="password"/>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="ChangePassword">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 p-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title">
|
||||||
|
<TL>Preferences</TL>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body fs-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input @bind="UserModel.StreamerMode" class="form-check-input" type="checkbox" role="switch" id="streamerModeSwitch">
|
||||||
|
<label class="form-check-label" for="streamerModeSwitch">
|
||||||
|
<TL>Streamer mode</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="text-end">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="SavePreferences">
|
||||||
|
</WButton>
|
||||||
|
</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="d-flex justify-content-center">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Continue"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Preparing"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="GenerateTwoFactorToken">
|
||||||
|
</WButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
|
<p>
|
||||||
<table class="w-100">
|
<TL>Scan the qr code and enter the code generated by the app you have scanned it in</TL>
|
||||||
<tr>
|
</p>
|
||||||
<td rowspan="2">
|
<div class="mt-3 text-center">
|
||||||
<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>Secure your account</TL>
|
|
||||||
</h4>
|
|
||||||
</td>
|
|
||||||
<td rowspan="2">
|
|
||||||
<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>
|
|
||||||
</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 class="modal fade" id="twofactorauth" tabindex="-1" style="display: none;" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header flex-stack py-6">
|
|
||||||
<h2 class="ms-3">
|
|
||||||
<TL>Activate 2fa</TL>
|
|
||||||
</h2>
|
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
|
||||||
<span class="svg-icon svg-icon-1">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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();
|
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||||
|
|
||||||
var qrCodeData = qrGenerator.CreateQrCode
|
var qrCodeData = qrGenerator.CreateQrCode
|
||||||
(
|
(
|
||||||
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={TotpSecret}&issuer={Uri.EscapeDataString(Issuer)}",
|
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={User.TotpSecret}&issuer={Uri.EscapeDataString("Moonlight")}",
|
||||||
QRCodeGenerator.ECCLevel.Q
|
QRCodeGenerator.ECCLevel.Q
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -135,184 +157,77 @@
|
|||||||
}
|
}
|
||||||
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
||||||
</div>
|
</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 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>
|
||||||
</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 class="modal fade" id="test" tabindex="-1" style="display: none;" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header flex-stack py-6">
|
|
||||||
<h2 class="ms-3">
|
|
||||||
<TL>Finish activation</TL>
|
|
||||||
</h2>
|
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
|
||||||
<span class="svg-icon svg-icon-1">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
|
|
||||||
<div class="text-gray-500 fw-semibold fs-6 mb-10">
|
|
||||||
<div class="alert alert-primary d-flex align-items-center p-5 mb-6">
|
|
||||||
<i class="bx bx-info-circle fs-2hx text-primary me-4">
|
|
||||||
<span class="path1"></span><span class="path2"></span>
|
|
||||||
</i>
|
|
||||||
<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 class="separator mt-2"></div>
|
|
||||||
|
|
||||||
<div class="alert alert-danger d-flex rounded ms-6 me-6 mt-6 mb-8 bg-body">
|
|
||||||
<div class="w-100">
|
|
||||||
<table>
|
|
||||||
<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>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</LazyLoader>
|
|
||||||
</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);
|
await UserService.ChangePassword(User, Password);
|
||||||
|
|
||||||
//TODO: AuditLog
|
|
||||||
|
|
||||||
// Reload to make the user login again
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
private async Task SavePreferences()
|
||||||
{
|
{
|
||||||
await AlertService.Error("Error", "Your password must be at least 8 characters and must contain a number");
|
User = Mapper.Map(User, UserModel);
|
||||||
}
|
UserRepository.Update(User);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -47,7 +47,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}")</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>
|
||||||
|
|||||||
@@ -1,211 +1,186 @@
|
|||||||
@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">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span class="badge badge-primary badge-lg px-5 me-4">Beta</span>
|
||||||
|
@if (EditMode)
|
||||||
{
|
{
|
||||||
if (UseSortedServerView)
|
<div>
|
||||||
{
|
<WButton Text="@(SmartTranslateService.Translate("New group"))"
|
||||||
var groupedServers = AllServers
|
WorkingText=""
|
||||||
.OrderBy(x => x.Name)
|
CssClasses="btn-primary me-3"
|
||||||
.GroupBy(x => x.Image.Name);
|
OnClick="AddGroup">
|
||||||
|
</WButton>
|
||||||
foreach (var groupedServer in groupedServers)
|
<WButton Text="@(SmartTranslateService.Translate("Finish editing layout"))"
|
||||||
{
|
CssClasses="btn-secondary"
|
||||||
<div class="separator separator-content my-15">@(groupedServer.Key)</div>
|
OnClick="async () => await SetEditMode(false)">
|
||||||
<div class="card card-body bg-secondary py-0 my-0 mx-0 px-0">
|
</WButton>
|
||||||
@foreach (var server in groupedServer)
|
|
||||||
{
|
|
||||||
<div class="row mx-4 my-4">
|
|
||||||
<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>
|
||||||
<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 class="d-none d-sm-block col my-auto fs-6">
|
|
||||||
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
|
||||||
</div>
|
|
||||||
<div class="d-none d-sm-block col my-auto 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
|
else
|
||||||
{
|
{
|
||||||
<span class="text-gray-400">
|
<WButton Text="@(SmartTranslateService.Translate("Edit layout"))"
|
||||||
<TL>Loading</TL>
|
CssClasses="btn-secondary"
|
||||||
</span>
|
OnClick="async () => await SetEditMode(true)">
|
||||||
|
</WButton>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
@foreach (var group in ServerGroups)
|
||||||
</div>
|
{
|
||||||
}
|
<div class="accordion my-3" id="serverListGroup@(group.GetHashCode())">
|
||||||
</div>
|
<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
|
else
|
||||||
{
|
{
|
||||||
foreach (var server in AllServers)
|
if (string.IsNullOrEmpty(group.Name))
|
||||||
{
|
{
|
||||||
<div class="row px-5 mb-5">
|
<TL>Unsorted servers</TL>
|
||||||
<a class="card card-body" href="/server/@(server.Uuid)">
|
}
|
||||||
<div class="row">
|
else
|
||||||
<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 class="d-none d-sm-block col my-auto fs-6">
|
|
||||||
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
|
||||||
</div>
|
|
||||||
<div class="d-none d-sm-block col my-auto fs-6">
|
|
||||||
@if (StatusCache.ContainsKey(server))
|
|
||||||
{
|
{
|
||||||
var status = StatusCache[server];
|
<span>@(group.Name)</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (EditMode)
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Remove group"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="async () => await RemoveGroup(group)">
|
||||||
|
</WButton>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="serverListGroup-body@(group.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverListGroup-header@(group.GetHashCode())" data-bs-parent="#serverListGroup">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row min-h-200px draggable-zone" ml-server-group="@(group.Name)">
|
||||||
|
@foreach (var id in group.Servers)
|
||||||
|
{
|
||||||
|
var server = AllServers.FirstOrDefault(x => x.Id.ToString() == id);
|
||||||
|
|
||||||
switch (status)
|
if (server != null)
|
||||||
{
|
{
|
||||||
case "offline":
|
<div class="col-12 col-md-3 p-3 draggable" ml-server-id="@(server.Id)">
|
||||||
<span class="text-danger">
|
@if (EditMode)
|
||||||
<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">
|
<div class="card bg-secondary">
|
||||||
<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">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<span class="badge badge-primary badge-lg">Beta</span>
|
<span class="card-label">@(server.Name)</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-toolbar">
|
||||||
|
<a href="#" class="btn btn-icon btn-sm btn-hover-light-primary draggable-handle">
|
||||||
|
<i class="bx bx-md bx-move"></i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<TL>Hidden in edit mode</TL>
|
||||||
<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>
|
||||||
<div class="form-check form-check-solid form-switch form-check-custom fv-row">
|
}
|
||||||
<input class="form-check-input w-45px h-30px" type="checkbox" id="sortedServerView" @bind="UseSortedServerView">
|
else
|
||||||
<label class="form-check-label" for="sortedServerView"></label>
|
{
|
||||||
|
<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>
|
||||||
|
|
||||||
@@ -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,21 @@ 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();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var server in AllServers)
|
foreach (var server in AllServers)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@@ -248,6 +240,109 @@ 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 EnsureAllServersInGroups();
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetEditMode(bool toggle)
|
||||||
|
{
|
||||||
|
EditMode = toggle;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
if (EditMode)
|
||||||
|
{
|
||||||
|
await EnsureAllServersInGroups();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(await GetGroupsFromClient());
|
||||||
|
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>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverGroup.Servers.Any(x => AllServers.All(y => y.Id.ToString() != x)))
|
||||||
|
{
|
||||||
|
Logger.Verbose("User tried to add a server in his server list which he has no access to");
|
||||||
|
return Array.Empty<ServerGroup>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task EnsureAllServersInGroups()
|
||||||
|
{
|
||||||
|
var presentInGroup = new List<Server>();
|
||||||
|
|
||||||
|
foreach (var group in ServerGroups)
|
||||||
|
{
|
||||||
|
foreach (var id in group.Servers)
|
||||||
|
presentInGroup.Add(AllServers.First(x => x.Id.ToString() == id));
|
||||||
|
}
|
||||||
|
|
||||||
|
var serversMissing = new List<Server>();
|
||||||
|
|
||||||
|
foreach (var server in AllServers)
|
||||||
|
{
|
||||||
|
if (presentInGroup.All(x => x.Id != server.Id))
|
||||||
|
serversMissing.Add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serversMissing.Any())
|
||||||
|
{
|
||||||
|
var defaultGroup = ServerGroups.FirstOrDefault(x => x.Name == "");
|
||||||
|
|
||||||
|
if (defaultGroup == null)
|
||||||
|
{
|
||||||
|
defaultGroup = new ServerGroup()
|
||||||
|
{
|
||||||
|
Name = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerGroups.Add(defaultGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var server in serversMissing)
|
||||||
|
defaultGroup.Servers.Add(server.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;Audit Log
|
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
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blur {
|
||||||
|
filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
div.wave {
|
div.wave {
|
||||||
}
|
}
|
||||||
div.wave .dot {
|
div.wave .dot {
|
||||||
|
|||||||
@@ -355,8 +355,6 @@
|
|||||||
{
|
{
|
||||||
// filter here what key events should be sent to moonlight
|
// filter here what key events should be sent to moonlight
|
||||||
|
|
||||||
console.log(event);
|
|
||||||
|
|
||||||
if(event.code === "KeyS" && event.ctrlKey)
|
if(event.code === "KeyS" && event.ctrlKey)
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -370,5 +368,54 @@
|
|||||||
{
|
{
|
||||||
window.removeEventListener('keydown', moonlight.keyListener.listener);
|
window.removeEventListener('keydown', moonlight.keyListener.listener);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
serverList: {
|
||||||
|
init: function ()
|
||||||
|
{
|
||||||
|
if(moonlight.serverList.Swappable)
|
||||||
|
{
|
||||||
|
moonlight.serverList.Swappable.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
let containers = document.querySelectorAll(".draggable-zone");
|
||||||
|
|
||||||
|
if (containers.length !== 0)
|
||||||
|
{
|
||||||
|
moonlight.serverList.Swappable = new Draggable.Sortable(containers, {
|
||||||
|
draggable: ".draggable",
|
||||||
|
handle: ".draggable .draggable-handle",
|
||||||
|
mirror: {
|
||||||
|
//appendTo: selector,
|
||||||
|
appendTo: "body",
|
||||||
|
constrainDimensions: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getData: function ()
|
||||||
|
{
|
||||||
|
let groups = new Array();
|
||||||
|
|
||||||
|
let groupElements = document.querySelectorAll('[ml-server-group]');
|
||||||
|
|
||||||
|
groupElements.forEach(groupElement => {
|
||||||
|
let group = new Object();
|
||||||
|
group.name = groupElement.attributes.getNamedItem("ml-server-group").value;
|
||||||
|
|
||||||
|
let servers = new Array();
|
||||||
|
let serverElements = groupElement.querySelectorAll("[ml-server-id]");
|
||||||
|
|
||||||
|
serverElements.forEach(serverElement => {
|
||||||
|
let id = serverElement.attributes.getNamedItem("ml-server-id").value;
|
||||||
|
|
||||||
|
servers.push(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
group.servers = servers;
|
||||||
|
groups.push(group);
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
32
README.md
32
README.md
@@ -67,6 +67,10 @@ Moonlight:
|
|||||||
Daemon (not wings):
|
Daemon (not wings):
|
||||||
`curl https://install.moonlightpanel.xyz/daemon| bash`
|
`curl https://install.moonlightpanel.xyz/daemon| bash`
|
||||||
|
|
||||||
|
Having any issues?
|
||||||
|
We are happy to help on our discord server:
|
||||||
|
[https://discord.gg/TJaspT7A8p](https://discord.gg/TJaspT7A8p)
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
The roudmap can be found here:
|
The roudmap can be found here:
|
||||||
@@ -96,3 +100,31 @@ Distributed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0
|
|||||||
* **Daniel Balk** - *Endelon Hosting* - [Daniel Balk](https://github.com/Daniel-Balk) - *Notification system & frontend*
|
* **Daniel Balk** - *Endelon Hosting* - [Daniel Balk](https://github.com/Daniel-Balk) - *Notification system & frontend*
|
||||||
* **Spielepapagei** - *Endelon Hosting* - [Spielepapagei](https://github.com/Spielepapagei) - *Discord Bot & support tickets*
|
* **Spielepapagei** - *Endelon Hosting* - [Spielepapagei](https://github.com/Spielepapagei) - *Discord Bot & support tickets*
|
||||||
* **Dannyx** - *None* - [Dannyx](https://github.com/Dannyx1604) - *Grammer check and translations*
|
* **Dannyx** - *None* - [Dannyx](https://github.com/Dannyx1604) - *Grammer check and translations*
|
||||||
|
|
||||||
|
## Some screenshots
|
||||||
|
Only user area
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|||||||
Reference in New Issue
Block a user