Просмотр исходного кода

Merge branch 'main' into AddLiveStatistics

Marcel Baumgartner 1 год назад
Родитель
Сommit
b99e8b5476
47 измененных файлов с 8462 добавлено и 3386 удалено
  1. 8 0
      Moonlight/App/Configuration/ConfigV1.cs
  2. 10 0
      Moonlight/App/Helpers/Wings/WingsConsole.cs
  3. 6 9
      Moonlight/App/MalwareScans/FakePlayerPluginScan.cs
  4. 6 9
      Moonlight/App/MalwareScans/MinerJarScan.cs
  5. 35 0
      Moonlight/App/MalwareScans/MinerScan.cs
  6. 6 9
      Moonlight/App/MalwareScans/SelfBotCodeScan.cs
  7. 6 9
      Moonlight/App/MalwareScans/SelfBotScan.cs
  8. 1 1
      Moonlight/App/Models/Misc/MalwareScan.cs
  9. 5 5
      Moonlight/App/Services/Background/MalwareBackgroundScanService.cs
  10. 77 13
      Moonlight/App/Services/Files/StorageService.cs
  11. 91 0
      Moonlight/App/Services/IpVerificationService.cs
  12. 8 13
      Moonlight/App/Services/MalwareScanService.cs
  13. 4 6
      Moonlight/App/Services/ServerService.cs
  14. 0 1
      Moonlight/Dockerfile
  15. 1 0
      Moonlight/Moonlight.csproj
  16. 12 7
      Moonlight/Pages/_Layout.cshtml
  17. 5 2
      Moonlight/Program.cs
  18. 4 4
      Moonlight/Properties/launchSettings.json
  19. 22 0
      Moonlight/Shared/Components/Navigations/AdminDomainsNavigation.razor
  20. 3 26
      Moonlight/Shared/Layouts/DefaultLayout.razor
  21. 36 22
      Moonlight/Shared/Layouts/MainLayout.razor
  22. 47 46
      Moonlight/Shared/Views/Admin/Domains/Index.razor
  23. 40 33
      Moonlight/Shared/Views/Admin/Domains/Shared/Index.razor
  24. 109 33
      Moonlight/Shared/Views/Admin/Security/Malware.razor
  25. 2 2
      Moonlight/Shared/Views/Admin/Servers/New.razor
  26. 1 1
      Moonlight/Shared/Views/Server/Index.razor
  27. 0 1
      Moonlight/defaultstorage/configs/default_subscription.json
  28. 0 778
      Moonlight/defaultstorage/resources/lang/de_de.lang
  29. 0 567
      Moonlight/defaultstorage/resources/lang/en_us.lang
  30. 0 778
      Moonlight/defaultstorage/resources/lang/fr_fr.lang
  31. 0 778
      Moonlight/defaultstorage/resources/lang/ro_ro.lang
  32. 0 53
      Moonlight/defaultstorage/resources/mail/login.html
  33. 0 53
      Moonlight/defaultstorage/resources/mail/passwordChange.html
  34. 0 54
      Moonlight/defaultstorage/resources/mail/passwordReset.html
  35. 0 50
      Moonlight/defaultstorage/resources/mail/register.html
  36. BIN
      Moonlight/defaultstorage/resources/public/background/main.jpg
  37. 0 14
      Moonlight/defaultstorage/resources/public/images/logo.svg
  38. BIN
      Moonlight/defaultstorage/resources/public/images/logolong.png
  39. 0 9
      Moonlight/wwwroot/assets/css/snow.css
  40. 372 0
      Moonlight/wwwroot/assets/css/toastr.css
  41. 5 0
      Moonlight/wwwroot/assets/js/apexcharts.js
  42. 5 0
      Moonlight/wwwroot/assets/js/bootstrap.min.js
  43. 7522 0
      Moonlight/wwwroot/assets/js/draggable.bundle.js
  44. 1 0
      Moonlight/wwwroot/assets/js/jquery.min.js
  45. 4 0
      Moonlight/wwwroot/assets/js/popper.min.js
  46. 0 0
      Moonlight/wwwroot/assets/js/toastr.min.js
  47. 8 0
      Moonlight/wwwroot/assets/js/xterm-addon-fit.min.js

+ 8 - 0
Moonlight/App/Configuration/ConfigV1.cs

@@ -304,6 +304,14 @@ public class ConfigV1
         public int BlockIpDuration { get; set; } = 15;
         public int BlockIpDuration { get; set; } = 15;
 
 
         [JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
         [JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
+
+        [JsonProperty("BlockDatacenterIps")]
+        [Description("If this option is enabled, users with an ip from datacenters will not be able to access the panel")]
+        public bool BlockDatacenterIps { get; set; } = true;
+
+        [JsonProperty("AllowCloudflareIps")]
+        [Description("Allow cloudflare ips to bypass the datacenter ip check")]
+        public bool AllowCloudflareIps { get; set; } = false;
     }
     }
 
 
     public class ReCaptchaData
     public class ReCaptchaData

+ 10 - 0
Moonlight/App/Helpers/Wings/WingsConsole.cs

@@ -218,6 +218,16 @@ public class WingsConsole : IDisposable
                         break;
                         break;
 
 
                     case "install output":
                     case "install output":
+                        if (ServerState != ServerState.Installing)
+                        {
+                            // Because wings is sending "install output" events BEFORE
+                            // sending the "install started" event,
+                            // we need to set the install state here
+                            // See https://github.com/pterodactyl/panel/issues/4853
+                            // for more details
+                            await UpdateServerState(ServerState.Installing);
+                        }
+                        
                         foreach (var line in eventData.Args)
                         foreach (var line in eventData.Args)
                         {
                         {
                             await SaveMessage(line);
                             await SaveMessage(line);

+ 6 - 9
Moonlight/App/MalwareScans/FakePlayerPluginScan.cs

@@ -9,7 +9,7 @@ public class FakePlayerPluginScan : MalwareScan
     public override string Name => "Fake player plugin scan";
     public override string Name => "Fake player plugin scan";
     public override string Description => "This scan is a simple fake player plugin scan provided by moonlight";
     public override string Description => "This scan is a simple fake player plugin scan provided by moonlight";
 
 
-    public override async Task<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
+    public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
     {
     {
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var access = await serverService.CreateFileAccess(server, null!);
         var access = await serverService.CreateFileAccess(server, null!);
@@ -24,19 +24,16 @@ public class FakePlayerPluginScan : MalwareScan
             {
             {
                 if (fileElement.Name.ToLower().Contains("fakeplayer"))
                 if (fileElement.Name.ToLower().Contains("fakeplayer"))
                 {
                 {
-                    return new[]
+                    return new()
                     {
                     {
-                        new MalwareScanResult
-                        {
-                            Title = "Fake player plugin",
-                            Description = $"Suspicious plugin file: {fileElement.Name}",
-                            Author = "Marcel Baumgartner"
-                        }
+                        Title = "Fake player plugin",
+                        Description = $"Suspicious plugin file: {fileElement.Name}",
+                        Author = "Marcel Baumgartner"
                     };
                     };
                 }
                 }
             }
             }
         }
         }
 
 
-        return Array.Empty<MalwareScanResult>();
+        return null;
     }
     }
 }
 }

+ 6 - 9
Moonlight/App/MalwareScans/MinerJarScan.cs

@@ -9,7 +9,7 @@ public class MinerJarScan : MalwareScan
     public override string Name => "Miner jar scan";
     public override string Name => "Miner jar scan";
     public override string Description => "This scan is a simple miner jar scan provided by moonlight";
     public override string Description => "This scan is a simple miner jar scan provided by moonlight";
 
 
-    public override async Task<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
+    public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
     {
     {
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var access = await serverService.CreateFileAccess(server, null!);
         var access = await serverService.CreateFileAccess(server, null!);
@@ -23,18 +23,15 @@ public class MinerJarScan : MalwareScan
 
 
             if (fileElements.Any(x => x.Name == "jdk" && !x.IsFile))
             if (fileElements.Any(x => x.Name == "jdk" && !x.IsFile))
             {
             {
-                return new[]
+                return new()
                 {
                 {
-                    new MalwareScanResult
-                    {
-                        Title = "Found Miner",
-                        Description = "Detected suspicious library directory which may contain a script for miners",
-                        Author = "Marcel Baumgartner"
-                    }
+                    Title = "Found Miner",
+                    Description = "Detected suspicious library directory which may contain a script for miners",
+                    Author = "Marcel Baumgartner"
                 };
                 };
             }
             }
         }
         }
 
 
-        return Array.Empty<MalwareScanResult>();
+        return null;
     }
     }
 }
 }

+ 35 - 0
Moonlight/App/MalwareScans/MinerScan.cs

@@ -0,0 +1,35 @@
+using Moonlight.App.Database.Entities;
+using Moonlight.App.Models.Misc;
+using Moonlight.App.Services;
+
+namespace Moonlight.App.MalwareScans;
+
+public class MinerScan : MalwareScan
+{
+    public override string Name => "Miner (NEZHA)";
+    public override string Description => "Probably a miner";
+    public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
+    {
+        var serverService = serviceProvider.GetRequiredService<ServerService>();
+
+        var access = await serverService.CreateFileAccess(server, null!);
+        var files = await access.Ls();
+
+        foreach (var file in files.Where(x => x.IsFile && (x.Name.EndsWith(".sh") || x.Name.EndsWith(".yml")) || x.Name == "bed"))
+        {
+            var content = await access.Read(file);
+
+            if (content.ToLower().Contains("nezha"))
+            {
+                return new()
+                {
+                    Title = "Miner",
+                    Description = "Miner start script (NEZHA)",
+                    Author = "Marcel Baumgartner"
+                };
+            }
+        }
+
+        return null;
+    }
+}

+ 6 - 9
Moonlight/App/MalwareScans/SelfBotCodeScan.cs

@@ -9,7 +9,7 @@ public class SelfBotCodeScan : MalwareScan
     public override string Name => "Selfbot code scan";
     public override string Name => "Selfbot code scan";
     public override string Description => "This scan is a simple selfbot code scan provided by moonlight";
     public override string Description => "This scan is a simple selfbot code scan provided by moonlight";
     
     
-    public override async Task<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
+    public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
     {
     {
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var access = await serverService.CreateFileAccess(server, null!);
         var access = await serverService.CreateFileAccess(server, null!);
@@ -21,18 +21,15 @@ public class SelfBotCodeScan : MalwareScan
 
 
             if (rawScript.Contains("https://discord.com/api") && !rawScript.Contains("https://discord.com/api/oauth2") && !rawScript.Contains("https://discord.com/api/webhook") || rawScript.Contains("https://rblxwild.com")) //TODO: Export to plugins, add regex for checking
             if (rawScript.Contains("https://discord.com/api") && !rawScript.Contains("https://discord.com/api/oauth2") && !rawScript.Contains("https://discord.com/api/webhook") || rawScript.Contains("https://rblxwild.com")) //TODO: Export to plugins, add regex for checking
             {
             {
-                return new[]
+                return new MalwareScanResult
                 {
                 {
-                    new MalwareScanResult
-                    {
-                        Title = "Potential selfbot",
-                        Description = $"Suspicious script file: {script.Name}",
-                        Author = "Marcel Baumgartner"
-                    }
+                    Title = "Potential selfbot",
+                    Description = $"Suspicious script file: {script.Name}",
+                    Author = "Marcel Baumgartner"
                 };
                 };
             }
             }
         }
         }
 
 
-        return Array.Empty<MalwareScanResult>();
+        return null;
     }
     }
 }
 }

+ 6 - 9
Moonlight/App/MalwareScans/SelfBotScan.cs

@@ -9,7 +9,7 @@ public class SelfBotScan : MalwareScan
     public override string Name => "Selfbot Scan";
     public override string Name => "Selfbot Scan";
     public override string Description => "This scan is a simple selfbot scan provided by moonlight";
     public override string Description => "This scan is a simple selfbot scan provided by moonlight";
 
 
-    public override async Task<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
+    public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
     {
     {
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var serverService = serviceProvider.GetRequiredService<ServerService>();
         var access = await serverService.CreateFileAccess(server, null!);
         var access = await serverService.CreateFileAccess(server, null!);
@@ -17,17 +17,14 @@ public class SelfBotScan : MalwareScan
 
 
         if (fileElements.Any(x => x.Name == "tokens.txt"))
         if (fileElements.Any(x => x.Name == "tokens.txt"))
         {
         {
-            return new[]
+            return new MalwareScanResult
             {
             {
-                new MalwareScanResult
-                {
-                    Title = "Found SelfBot",
-                    Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot",
-                    Author = "Marcel Baumgartner"
-                }
+                Title = "Found SelfBot",
+                Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot",
+                Author = "Marcel Baumgartner"
             };
             };
         }
         }
 
 
-        return Array.Empty<MalwareScanResult>();
+        return null;
     }
     }
 }
 }

+ 1 - 1
Moonlight/App/Models/Misc/MalwareScan.cs

@@ -6,5 +6,5 @@ public abstract class MalwareScan
 {
 {
     public abstract string Name { get; }
     public abstract string Name { get; }
     public abstract string Description { get; }
     public abstract string Description { get; }
-    public abstract Task<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider);
+    public abstract Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider);
 }
 }

+ 5 - 5
Moonlight/App/Services/Background/MalwareBackgroundScanService.cs

@@ -15,7 +15,7 @@ public class MalwareBackgroundScanService
 
 
     public bool IsRunning => !ScanTask?.IsCompleted ?? false;
     public bool IsRunning => !ScanTask?.IsCompleted ?? false;
     public bool ScanAllServers { get; set; }
     public bool ScanAllServers { get; set; }
-    public readonly Dictionary<Server, MalwareScanResult[]> ScanResults;
+    public readonly Dictionary<Server, MalwareScanResult> ScanResults;
     public string Status { get; private set; } = "N/A";
     public string Status { get; private set; } = "N/A";
 
 
     private Task? ScanTask;
     private Task? ScanTask;
@@ -70,16 +70,16 @@ public class MalwareBackgroundScanService
             Status = $"[{i} / {servers.Length}] Scanning server {server.Name}";
             Status = $"[{i} / {servers.Length}] Scanning server {server.Name}";
             await Event.Emit("malwareScan.status", IsRunning);
             await Event.Emit("malwareScan.status", IsRunning);
             
             
-            var results = await malwareScanService.Perform(server);
+            var result = await malwareScanService.Perform(server);
 
 
-            if (results.Any())
+            if (result != null)
             {
             {
                 lock (ScanResults)
                 lock (ScanResults)
                 {
                 {
-                    ScanResults.Add(server, results);
+                    ScanResults.Add(server, result);
                 }
                 }
                 
                 
-                await Event.Emit("malwareScan.result");
+                await Event.Emit("malwareScan.result", server);
             }
             }
 
 
             i++;
             i++;

+ 77 - 13
Moonlight/App/Services/Files/StorageService.cs

@@ -1,15 +1,11 @@
 using Moonlight.App.Helpers;
 using Moonlight.App.Helpers;
+using Octokit;
 
 
 namespace Moonlight.App.Services.Files;
 namespace Moonlight.App.Services.Files;
 
 
 public class StorageService
 public class StorageService
 {
 {
-    public StorageService()
-    {
-        EnsureCreated();
-    }
-    
-    public void EnsureCreated()
+    public async Task EnsureCreated()
     {
     {
         Directory.CreateDirectory(PathBuilder.Dir("storage", "uploads"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "uploads"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "configs"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "configs"));
@@ -17,13 +13,16 @@ public class StorageService
         Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
         Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
-        
-        if(IsEmpty(PathBuilder.Dir("storage", "resources")))
+
+        await UpdateResources();
+
+        return;
+        if (IsEmpty(PathBuilder.Dir("storage", "resources")))
         {
         {
             Logger.Info("Default resources not found. Copying default resources");
             Logger.Info("Default resources not found. Copying default resources");
-            
+
             CopyFilesRecursively(
             CopyFilesRecursively(
-                PathBuilder.Dir("defaultstorage", "resources"), 
+                PathBuilder.Dir("defaultstorage", "resources"),
                 PathBuilder.Dir("storage", "resources")
                 PathBuilder.Dir("storage", "resources")
             );
             );
         }
         }
@@ -31,18 +30,83 @@ public class StorageService
         if (IsEmpty(PathBuilder.Dir("storage", "configs")))
         if (IsEmpty(PathBuilder.Dir("storage", "configs")))
         {
         {
             Logger.Info("Default configs not found. Copying default configs");
             Logger.Info("Default configs not found. Copying default configs");
-            
+
             CopyFilesRecursively(
             CopyFilesRecursively(
-                PathBuilder.Dir("defaultstorage", "configs"), 
+                PathBuilder.Dir("defaultstorage", "configs"),
                 PathBuilder.Dir("storage", "configs")
                 PathBuilder.Dir("storage", "configs")
             );
             );
         }
         }
     }
     }
 
 
+    private async Task UpdateResources()
+    {
+        try
+        {
+            Logger.Info("Checking resources");
+
+            var client = new GitHubClient(
+                new ProductHeaderValue("Moonlight-Panel"));
+
+            var user = "Moonlight-Panel";
+            var repo = "Resources";
+            var resourcesDir = PathBuilder.Dir("storage", "resources");
+
+            async Task CopyDirectory(string dirPath, string localDir)
+            {
+                IReadOnlyList<RepositoryContent> contents;
+
+                if (string.IsNullOrEmpty(dirPath))
+                    contents = await client.Repository.Content.GetAllContents(user, repo);
+                else
+                    contents = await client.Repository.Content.GetAllContents(user, repo, dirPath);
+
+                foreach (var content in contents)
+                {
+                    string localPath = Path.Combine(localDir, content.Name);
+
+                    if (content.Type == ContentType.File)
+                    {
+                        if (content.Name.EndsWith(".gitattributes"))
+                            continue;
+
+                        if (File.Exists(localPath) && !content.Name.EndsWith(".lang"))
+                            continue;
+
+                        if (content.Name.EndsWith(".lang") && File.Exists(localPath) &&
+                            new FileInfo(localPath).Length == content.Size)
+                            continue;
+
+                        var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path);
+                        Directory.CreateDirectory(localDir); // Ensure the directory exists
+                        await File.WriteAllBytesAsync(localPath, fileContent);
+
+                        Logger.Debug($"Synced file '{content.Path}'");
+                    }
+                    else if (content.Type == ContentType.Dir)
+                    {
+                        await CopyDirectory(content.Path, localPath);
+                    }
+                }
+            }
+
+            await CopyDirectory("", resourcesDir);
+        }
+        catch (RateLimitExceededException)
+        {
+            Logger.Warn("Unable to sync resources due to your ip being rate-limited by github");
+        }
+        catch (Exception e)
+        {
+            Logger.Warn("Unable to sync resources");
+            Logger.Warn(e);
+        }
+    }
+
     private bool IsEmpty(string path)
     private bool IsEmpty(string path)
     {
     {
         return !Directory.EnumerateFileSystemEntries(path).Any();
         return !Directory.EnumerateFileSystemEntries(path).Any();
     }
     }
+
     private static void CopyFilesRecursively(string sourcePath, string targetPath)
     private static void CopyFilesRecursively(string sourcePath, string targetPath)
     {
     {
         //Now Create all of the directories
         //Now Create all of the directories
@@ -52,7 +116,7 @@ public class StorageService
         }
         }
 
 
         //Copy all the files & Replaces any files with the same name
         //Copy all the files & Replaces any files with the same name
-        foreach (string newPath in Directory.GetFiles(sourcePath, "*.*",SearchOption.AllDirectories))
+        foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
         {
         {
             File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true);
             File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true);
         }
         }

+ 91 - 0
Moonlight/App/Services/IpVerificationService.cs

@@ -0,0 +1,91 @@
+using Moonlight.App.Helpers;
+using Whois.NET;
+
+namespace Moonlight.App.Services;
+
+public class IpVerificationService
+{
+    private readonly ConfigService ConfigService;
+
+    public IpVerificationService(ConfigService configService)
+    {
+        ConfigService = configService;
+    }
+
+    public async Task<bool> IsDatacenterOrVpn(string ip)
+    {
+        if (!ConfigService.Get().Moonlight.Security.BlockDatacenterIps)
+            return false;
+        
+        if (string.IsNullOrEmpty(ip))
+            return false;
+
+        var datacenterNames = new List<string>()
+        {
+            "amazon",
+            "aws",
+            "microsoft",
+            "azure",
+            "google",
+            "google cloud",
+            "gcp",
+            "digitalocean",
+            "linode",
+            "vultr",
+            "ovh",
+            "ovhcloud",
+            "alibaba",
+            "oracle",
+            "ibm cloud",
+            "bluehost",
+            "godaddy",
+            "rackpace",
+            "hetzner",
+            "tencent",
+            "scaleway",
+            "softlayer",
+            "dreamhost",
+            "a2 hosting",
+            "inmotion hosting",
+            "red hat openstack",
+            "kamatera",
+            "hostgator",
+            "siteground",
+            "greengeeks",
+            "liquidweb",
+            "joyent",
+            "aruba",
+            "interoute",
+            "fastcomet",
+            "rosehosting",
+            "lunarpages",
+            "fatcow",
+            "jelastic",
+            "datacamp"
+        };
+        
+        if(!ConfigService.Get().Moonlight.Security.AllowCloudflareIps)
+            datacenterNames.Add("cloudflare");
+
+        try
+        {
+            var response = await WhoisClient.QueryAsync(ip);
+            var responseText = response.Raw.ToLower();
+
+            foreach (var name in datacenterNames)
+            {
+                if (responseText.Contains(name))
+                {
+                    Logger.Debug(name);
+                    return true;
+                }
+            }
+        }
+        catch (Exception)
+        {
+            return false;
+        }
+
+        return false;
+    }
+}

+ 8 - 13
Moonlight/App/Services/MalwareScanService.cs

@@ -5,7 +5,7 @@ using Moonlight.App.Services.Plugins;
 
 
 namespace Moonlight.App.Services;
 namespace Moonlight.App.Services;
 
 
-public class MalwareScanService //TODO: Make this moddable using plugins
+public class MalwareScanService
 {
 {
     private readonly PluginService PluginService;
     private readonly PluginService PluginService;
     private readonly IServiceScopeFactory ServiceScopeFactory;
     private readonly IServiceScopeFactory ServiceScopeFactory;
@@ -16,34 +16,29 @@ public class MalwareScanService //TODO: Make this moddable using plugins
         ServiceScopeFactory = serviceScopeFactory;
         ServiceScopeFactory = serviceScopeFactory;
     }
     }
 
 
-    public async Task<MalwareScanResult[]> Perform(Server server)
+    public async Task<MalwareScanResult?> Perform(Server server)
     {
     {
         var defaultScans = new List<MalwareScan>
         var defaultScans = new List<MalwareScan>
         {
         {
             new SelfBotScan(),
             new SelfBotScan(),
             new MinerJarScan(),
             new MinerJarScan(),
             new SelfBotCodeScan(),
             new SelfBotCodeScan(),
-            new FakePlayerPluginScan()
+            new FakePlayerPluginScan(),
+            new MinerScan()
         };
         };
 
 
         var scans = await PluginService.BuildMalwareScans(defaultScans.ToArray());
         var scans = await PluginService.BuildMalwareScans(defaultScans.ToArray());
-
-        var results = new List<MalwareScanResult>();
+        
         using var scope = ServiceScopeFactory.CreateScope();
         using var scope = ServiceScopeFactory.CreateScope();
         
         
         foreach (var scan in scans)
         foreach (var scan in scans)
         {
         {
             var result = await scan.Scan(server, scope.ServiceProvider);
             var result = await scan.Scan(server, scope.ServiceProvider);
 
 
-            if (result.Any())
-            {
-                foreach (var scanResult in result)
-                {
-                    results.Add(scanResult);
-                }
-            }
+            if (result != null)
+                return result;
         }
         }
 
 
-        return results.ToArray();
+        return null;
     }
     }
 }
 }

+ 4 - 6
Moonlight/App/Services/ServerService.cs

@@ -113,19 +113,17 @@ public class ServerService
         if (ConfigService.Get().Moonlight.Security.MalwareCheckOnStart && signal == PowerSignal.Start ||
         if (ConfigService.Get().Moonlight.Security.MalwareCheckOnStart && signal == PowerSignal.Start ||
             signal == PowerSignal.Restart)
             signal == PowerSignal.Restart)
         {
         {
-            var results = await new MalwareScanService(
+            var result = await new MalwareScanService(
                 PluginService,
                 PluginService,
                 ServiceScopeFactory
                 ServiceScopeFactory
             ).Perform(server);
             ).Perform(server);
 
 
-            if (results.Any())
+            if (result != null)
             {
             {
-                var resultText = string.Join(" ", results.Select(x => x.Title));
-
-                Logger.Warn($"Found malware on server {server.Uuid}. Results: " + resultText);
+                Logger.Warn($"Found malware on server {server.Uuid}. Result: " + result.Title);
 
 
                 throw new DisplayException(
                 throw new DisplayException(
-                    $"Unable to start server. Found following malware on this server: {resultText}. Please contact the support if you think this detection is a false positive",
+                    $"Unable to start server. Found following malware on this server: {result.Title}. Please contact the support if you think this detection is a false positive",
                     true);
                     true);
             }
             }
         }
         }

+ 0 - 1
Moonlight/Dockerfile

@@ -22,5 +22,4 @@ COPY --from=publish /app/publish .
 RUN mkdir -p /app/storage
 RUN mkdir -p /app/storage
 RUN touch /app/storage/donttriggeranyerrors
 RUN touch /app/storage/donttriggeranyerrors
 RUN rm -r /app/storage/*
 RUN rm -r /app/storage/*
-COPY "Moonlight/defaultstorage" "/app/defaultstorage"
 ENTRYPOINT ["dotnet", "Moonlight.dll"]
 ENTRYPOINT ["dotnet", "Moonlight.dll"]

+ 1 - 0
Moonlight/Moonlight.csproj

@@ -55,6 +55,7 @@
     <PackageReference Include="SSH.NET" Version="2020.0.2" />
     <PackageReference Include="SSH.NET" Version="2020.0.2" />
     <PackageReference Include="Stripe.net" Version="41.23.0-beta.1" />
     <PackageReference Include="Stripe.net" Version="41.23.0-beta.1" />
     <PackageReference Include="UAParser" Version="3.1.47" />
     <PackageReference Include="UAParser" Version="3.1.47" />
+    <PackageReference Include="WhoisClient.NET" Version="5.0.0" />
     <PackageReference Include="XtermBlazor" Version="1.8.1" />
     <PackageReference Include="XtermBlazor" Version="1.8.1" />
   </ItemGroup>
   </ItemGroup>
 
 

+ 12 - 7
Moonlight/Pages/_Layout.cshtml

@@ -1,4 +1,4 @@
-@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web
 @using Moonlight.App.Extensions
 @using Moonlight.App.Extensions
 @using Moonlight.App.Repositories
 @using Moonlight.App.Repositories
 @using Moonlight.App.Services
 @using Moonlight.App.Services
@@ -41,7 +41,6 @@
 
 
     <link rel="stylesheet" type="text/css" href="/assets/css/style.bundle.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/style.bundle.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/flashbang.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/flashbang.css"/>
-    <link rel="stylesheet" type="text/css" href="/assets/css/snow.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/utils.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/utils.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/boxicons.min.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/boxicons.min.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/blazor.css"/>
     <link rel="stylesheet" type="text/css" href="/assets/css/blazor.css"/>
@@ -49,6 +48,7 @@
     <link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
     <link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
     <link rel="stylesheet" type="text/css" href="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css"/>
     <link rel="stylesheet" type="text/css" href="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css"/>
     <link rel="stylesheet" type="text/css" href="/_content/Blazor.ContextMenu/blazorContextMenu.min.css"/>
     <link rel="stylesheet" type="text/css" href="/_content/Blazor.ContextMenu/blazorContextMenu.min.css"/>
+    <link rel="stylesheet" type="text/css" href="/assets/css/toastr.css"/>
     
     
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
     <base href="~/"/>
     <base href="~/"/>
@@ -94,8 +94,8 @@
     </div>
     </div>
 </div>
 </div>
 
 
-<script src="https://cdn.jsdelivr.net/npm/@@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous"></script>
-<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
+<script src="/assets/js/popper.min.js"></script>
+<script src="/assets/js/bootstrap.min.js"></script>
 
 
 <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>
@@ -105,12 +105,15 @@
 <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="/assets/js/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://code.jquery.com/jquery-3.7.0.min.js"></script>
+<script src="/assets/js/xterm-addon-fit.min.js"></script>
+<script src="/assets/js/jquery.min.js"></script>
+<script src="/assets/js/toastr.min.js"></script>
+
+<script src="/assets/js/apexcharts.js"></script>
 
 
 <script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
 <script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
 <script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
 <script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
@@ -123,6 +126,8 @@
 moonlight.loading.registerXterm();
 moonlight.loading.registerXterm();
 </script>
 </script>
 
 
+<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
+
 <script src="/_framework/blazor.server.js"></script>
 <script src="/_framework/blazor.server.js"></script>
 
 
 </body>
 </body>

+ 5 - 2
Moonlight/Program.cs

@@ -42,8 +42,10 @@ namespace Moonlight
     {
     {
         public static async Task Main(string[] args)
         public static async Task Main(string[] args)
         {
         {
-            // This will also copy all default config files
-            var configService = new ConfigService(new StorageService());
+            var storageService = new StorageService();
+            await storageService.EnsureCreated();
+            
+            var configService = new ConfigService(storageService);
             var shouldUseSentry = configService
             var shouldUseSentry = configService
                 .Get()
                 .Get()
                 .Moonlight.Sentry.Enable;
                 .Moonlight.Sentry.Enable;
@@ -218,6 +220,7 @@ namespace Moonlight
             builder.Services.AddScoped<TicketClientService>();
             builder.Services.AddScoped<TicketClientService>();
             builder.Services.AddScoped<TicketAdminService>();
             builder.Services.AddScoped<TicketAdminService>();
             builder.Services.AddScoped<MalwareScanService>();
             builder.Services.AddScoped<MalwareScanService>();
+            builder.Services.AddSingleton<IpVerificationService>();
 
 
             builder.Services.AddScoped<SessionClientService>();
             builder.Services.AddScoped<SessionClientService>();
             builder.Services.AddSingleton<SessionServerService>();
             builder.Services.AddSingleton<SessionServerService>();

+ 4 - 4
Moonlight/Properties/launchSettings.json

@@ -9,7 +9,7 @@
         "ASPNETCORE_ENVIRONMENT": "Development",
         "ASPNETCORE_ENVIRONMENT": "Development",
         "ML_DEBUG": "true"
         "ML_DEBUG": "true"
       },
       },
-      "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
+      "applicationUrl": "http://moonlight.owo:5118;https://localhost:7118;http://localhost:5118",
       "dotnetRunMessages": true
       "dotnetRunMessages": true
     },
     },
     "Live DB": 
     "Live DB": 
@@ -21,7 +21,7 @@
         "ML_DEBUG": "true",
         "ML_DEBUG": "true",
         "ML_CONFIG_PATH": "storage\\configs\\live_config.json"
         "ML_CONFIG_PATH": "storage\\configs\\live_config.json"
       },
       },
-      "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
+      "applicationUrl": "http://moonlight.owo:5118;https://localhost:7118;http://localhost:5118",
       "dotnetRunMessages": true
       "dotnetRunMessages": true
     },
     },
     "Dev DB 1":
     "Dev DB 1":
@@ -33,7 +33,7 @@
         "ML_DEBUG": "true",
         "ML_DEBUG": "true",
         "ML_CONFIG_PATH": "storage\\configs\\dev_1_config.json"
         "ML_CONFIG_PATH": "storage\\configs\\dev_1_config.json"
       },
       },
-      "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
+      "applicationUrl": "http://moonlight.owo:5118;https://localhost:7118;http://localhost:5118",
       "dotnetRunMessages": true
       "dotnetRunMessages": true
     },
     },
     "Dev DB 2":
     "Dev DB 2":
@@ -45,7 +45,7 @@
         "ML_DEBUG": "true",
         "ML_DEBUG": "true",
         "ML_CONFIG_PATH": "storage\\configs\\dev_2_config.json"
         "ML_CONFIG_PATH": "storage\\configs\\dev_2_config.json"
       },
       },
-      "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
+      "applicationUrl": "http://moonlight.owo:5118;https://localhost:7118;http://localhost:5118",
       "dotnetRunMessages": true
       "dotnetRunMessages": true
     }
     }
   }
   }

+ 22 - 0
Moonlight/Shared/Components/Navigations/AdminDomainsNavigation.razor

@@ -0,0 +1,22 @@
+<div class="card mb-5 mb-xl-10">
+    <div class="card-body pt-0 pb-0">
+        <ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
+            <li class="nav-item mt-2">
+                <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/domains">
+                    <TL>Domains</TL>
+                </a>
+            </li>
+            <li class="nav-item mt-2">
+                <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/domains/shared">
+                    <TL>Shared domains</TL>
+                </a>
+            </li>
+        </ul>
+    </div>
+</div>
+
+@code
+{
+    [Parameter]
+    public int Index { get; set; } = 0;
+}

+ 3 - 26
Moonlight/Shared/Layouts/DefaultLayout.razor

@@ -187,38 +187,15 @@ else
                 </span>
                 </span>
             </a>
             </a>
         </div>
         </div>
-        <div data-kt-menu-trigger="click" class="menu-item menu-accordion">
-            <span class="menu-link">
+        <div class="menu-item">
+            <a class="menu-link" href="/admin/domains">
                 <span class="menu-icon">
                 <span class="menu-icon">
                     <i class="bx bx-purchase-tag"></i>
                     <i class="bx bx-purchase-tag"></i>
                 </span>
                 </span>
                 <span class="menu-title">
                 <span class="menu-title">
                     <TL>Domains</TL>
                     <TL>Domains</TL>
                 </span>
                 </span>
-                <span class="menu-arrow"></span>
-            </span>
-            <div class="menu-sub menu-sub-accordion">
-                <div class="menu-item">
-                    <a class="menu-link" href="/admin/domains/">
-                        <span class="menu-bullet">
-                            <span class="bullet bullet-dot"></span>
-                        </span>
-                        <span class="menu-title">
-                            <TL>Domains</TL>
-                        </span>
-                    </a>
-                </div>
-                <div class="menu-item">
-                    <a class="menu-link" href="/admin/domains/shared">
-                        <span class="menu-bullet">
-                            <span class="bullet bullet-dot"></span>
-                        </span>
-                        <span class="menu-title">
-                            <TL>Shared domains</TL>
-                        </span>
-                    </a>
-                </div>
-            </div>
+            </a>
         </div>
         </div>
         <div class="menu-item">
         <div class="menu-item">
             <a class="menu-link" href="/admin/support">
             <a class="menu-link" href="/admin/support">

+ 36 - 22
Moonlight/Shared/Layouts/MainLayout.razor

@@ -22,6 +22,7 @@
 @inject DynamicBackgroundService DynamicBackgroundService
 @inject DynamicBackgroundService DynamicBackgroundService
 @inject KeyListenerService KeyListenerService
 @inject KeyListenerService KeyListenerService
 @inject ConfigService ConfigService
 @inject ConfigService ConfigService
+@inject IpVerificationService IpVerificationService
 
 
 @{
 @{
     var uri = new Uri(NavigationManager.Uri);
     var uri = new Uri(NavigationManager.Uri);
@@ -46,9 +47,35 @@
 
 
     <DefaultLayout>
     <DefaultLayout>
         <SoftErrorBoundary>
         <SoftErrorBoundary>
-            @if (!IsIpBanned)
+            @if (UserProcessed)
             {
             {
-                if (UserProcessed)
+                if (IsIpBanned)
+                {
+                    <div class="modal d-block">
+                        <div class="modal-dialog modal-dialog-centered mw-900px">
+                            <div class="modal-content">
+                                <div class="pt-2 modal-body py-lg-10 px-lg-10">
+                                    <h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
+                                    <p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                }
+                else if (IsIpSuspicious)
+                {
+                    <div class="modal d-block">
+                        <div class="modal-dialog modal-dialog-centered mw-900px">
+                            <div class="modal-content">
+                                <div class="pt-2 modal-body py-lg-10 px-lg-10">
+                                    <h2>@(SmartTranslateService.Translate("Your ip his blocked. VPNs and Datacenter IPs are prohibited from accessing this site"))</h2>
+                                    <p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Please disable your vpn or proxy and try it again"))</p>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                }
+                else
                 {
                 {
                     if (uri.LocalPath != "/login" &&
                     if (uri.LocalPath != "/login" &&
                         uri.LocalPath != "/passwordreset" &&
                         uri.LocalPath != "/passwordreset" &&
@@ -102,19 +129,6 @@
                         }
                         }
                     }
                     }
                 }
                 }
-                else
-                {
-                    <div class="modal d-block">
-                        <div class="modal-dialog modal-dialog-centered mw-900px">
-                            <div class="modal-content">
-                                <div class="pt-2 modal-body py-lg-10 px-lg-10">
-                                    <h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
-                                    <p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                }
             }
             }
             else
             else
             {
             {
@@ -122,8 +136,8 @@
                     <div class="modal-dialog modal-dialog-centered mw-900px">
                     <div class="modal-dialog modal-dialog-centered mw-900px">
                         <div class="modal-content">
                         <div class="modal-content">
                             <div class="pt-2 modal-body py-lg-10 px-lg-10">
                             <div class="pt-2 modal-body py-lg-10 px-lg-10">
-                                <h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
-                                <p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
+                                <h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
+                                <p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
                             </div>
                             </div>
                         </div>
                         </div>
                     </div>
                     </div>
@@ -137,6 +151,7 @@
 {
 {
     private bool UserProcessed = false;
     private bool UserProcessed = false;
     private bool IsIpBanned = false;
     private bool IsIpBanned = false;
+    private bool IsIpSuspicious = false;
 
 
     protected override async Task OnAfterRenderAsync(bool firstRender)
     protected override async Task OnAfterRenderAsync(bool firstRender)
     {
     {
@@ -146,11 +161,6 @@
             {
             {
                 DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) => { await InvokeAsync(StateHasChanged); };
                 DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) => { await InvokeAsync(StateHasChanged); };
 
 
-                IsIpBanned = await IpBanService.IsBanned();
-
-                if (IsIpBanned)
-                    await InvokeAsync(StateHasChanged);
-
                 await Event.On<Object>("ipBan.update", this, async _ =>
                 await Event.On<Object>("ipBan.update", this, async _ =>
                 {
                 {
                     IsIpBanned = await IpBanService.IsBanned();
                     IsIpBanned = await IpBanService.IsBanned();
@@ -158,6 +168,10 @@
                 });
                 });
 
 
                 await IdentityService.Load();
                 await IdentityService.Load();
+
+                IsIpBanned = await IpBanService.IsBanned();
+                IsIpSuspicious = await IpVerificationService.IsDatacenterOrVpn(IdentityService.Ip);
+
                 UserProcessed = true;
                 UserProcessed = true;
                 await InvokeAsync(StateHasChanged);
                 await InvokeAsync(StateHasChanged);
 
 

+ 47 - 46
Moonlight/Shared/Views/Admin/Domains/Index.razor

@@ -4,6 +4,7 @@
 @using Microsoft.EntityFrameworkCore
 @using Microsoft.EntityFrameworkCore
 @using BlazorTable
 @using BlazorTable
 @using Moonlight.App.Services
 @using Moonlight.App.Services
+@using Moonlight.Shared.Components.Navigations
 
 
 @inject DomainRepository DomainRepository
 @inject DomainRepository DomainRepository
 @inject DomainService DomainService
 @inject DomainService DomainService
@@ -11,56 +12,56 @@
 
 
 @attribute [PermissionRequired(nameof(Permissions.AdminDomains))]
 @attribute [PermissionRequired(nameof(Permissions.AdminDomains))]
 
 
+<AdminDomainsNavigation Index="0" />
+
 <LazyLoader @ref="LazyLoader" Load="Load">
 <LazyLoader @ref="LazyLoader" Load="Load">
-    <div class="row">
-        <div class="card">
-            <div class="card-header border-0 pt-5">
-                <h3 class="card-title align-items-start flex-column">
-                    <span class="card-label fw-bold fs-3 mb-1">
-                        <TL>Domains</TL>
-                    </span>
-                </h3>
-                <div class="card-toolbar">
-                    <a href="/admin/domains/new" class="btn btn-sm btn-light-success">
-                        <i class="bx bx-layer-plus"></i>
-                        <TL>New domain</TL>
-                    </a>
+    <div class="card">
+                <div class="card-header border-0 pt-5">
+                    <h3 class="card-title align-items-start flex-column">
+                        <span class="card-label fw-bold fs-3 mb-1">
+                            <TL>Domains</TL>
+                        </span>
+                    </h3>
+                    <div class="card-toolbar">
+                        <a href="/admin/domains/new" class="btn btn-sm btn-light-success">
+                            <i class="bx bx-layer-plus"></i>
+                            <TL>New domain</TL>
+                        </a>
+                    </div>
                 </div>
                 </div>
-            </div>
-            <div class="card-body pt-0">
-                <div class="table-responsive">
-                    <Table TableItem="Domain" Items="Domains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
-                        <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
-                        <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
-                        <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
-                            <Template>
-                                <span>@(context.SharedDomain.Name)</span>
-                            </Template>
-                        </Column>
-                        <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
-                            <Template>
-                                <a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
-                            </Template>
-                        </Column>
-                        <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
-                            <Template>
-                                <a href="/domain/@(context.Id)">Manage</a>
-                            </Template>
-                        </Column>
-                        <Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
-                            <Template>
-                                <DeleteButton Confirm="true"
-                                              AdditionalCssClasses="float-end"
-                                              OnClick="() => Delete(context)">
-                                </DeleteButton>
-                            </Template>
-                        </Column>
-                        <Pager ShowPageNumber="true" ShowTotalCount="true"/>
-                    </Table>
+                <div class="card-body pt-0">
+                    <div class="table-responsive">
+                        <Table TableItem="Domain" Items="Domains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
+                            <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
+                            <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
+                            <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
+                                <Template>
+                                    <span>@(context.SharedDomain.Name)</span>
+                                </Template>
+                            </Column>
+                            <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
+                                <Template>
+                                    <a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
+                                </Template>
+                            </Column>
+                            <Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
+                                <Template>
+                                    <a href="/domain/@(context.Id)">Manage</a>
+                                </Template>
+                            </Column>
+                            <Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
+                                <Template>
+                                    <DeleteButton Confirm="true"
+                                                  AdditionalCssClasses="float-end"
+                                                  OnClick="() => Delete(context)">
+                                    </DeleteButton>
+                                </Template>
+                            </Column>
+                            <Pager ShowPageNumber="true" ShowTotalCount="true"/>
+                        </Table>
+                    </div>
                 </div>
                 </div>
             </div>
             </div>
-        </div>
-    </div>
 </LazyLoader>
 </LazyLoader>
 
 
 @code
 @code

+ 40 - 33
Moonlight/Shared/Views/Admin/Domains/Shared/Index.razor

@@ -5,6 +5,7 @@
 @using Moonlight.App.Database.Entities
 @using Moonlight.App.Database.Entities
 @using Moonlight.App.Services.Interop
 @using Moonlight.App.Services.Interop
 @using BlazorTable
 @using BlazorTable
+@using Moonlight.Shared.Components.Navigations
 
 
 @inject SharedDomainRepository SharedDomainRepository
 @inject SharedDomainRepository SharedDomainRepository
 @inject SmartTranslateService SmartTranslateService
 @inject SmartTranslateService SmartTranslateService
@@ -14,37 +15,43 @@
 
 
 @attribute [PermissionRequired(nameof(Permissions.AdminSharedDomains))]
 @attribute [PermissionRequired(nameof(Permissions.AdminSharedDomains))]
 
 
+<AdminDomainsNavigation Index="1" />
+
 <LazyLoader @ref="LazyLoader" Load="Load">
 <LazyLoader @ref="LazyLoader" Load="Load">
-                    <div class="card">
-                        <div class="card-header border-0 pt-5">
-                            <h3 class="card-title align-items-start flex-column">
-                                <span class="card-label fw-bold fs-3 mb-1">
-                                    <span><TL>Shared domains</TL></span>
-                                </span>
-                            </h3>
-                            <div class="card-toolbar">
-                                <a href="/admin/domains/shared/new" class="btn btn-sm btn-light-success">
-                                    <i class="bx bx-layer-plus"></i>
-                                    <span><TL>Add shared domain</TL></span>
-                                </a>
-                            </div>
-                        </div>
-                        <div class="card-body">
-                            <Table TableItem="SharedDomain" Items="SharedDomains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
-                                <Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
-                                <Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
-                                <Column TableItem="SharedDomain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
-                                    <Template>
-                                        <DeleteButton Confirm="true"
-                                                      AdditionalCssClasses="float-end"
-                                                      OnClick="() => Delete(context)">
-                                        </DeleteButton>
-                                    </Template>
-                                </Column>
-                            </Table>
-                        </div>
-                    </div>
-                </LazyLoader>
+    <div class="card">
+        <div class="card-header border-0 pt-5">
+            <h3 class="card-title align-items-start flex-column">
+                <span class="card-label fw-bold fs-3 mb-1">
+                    <span>
+                        <TL>Shared domains</TL>
+                    </span>
+                </span>
+            </h3>
+            <div class="card-toolbar">
+                <a href="/admin/domains/shared/new" class="btn btn-sm btn-light-success">
+                    <i class="bx bx-layer-plus"></i>
+                    <span>
+                        <TL>Add shared domain</TL>
+                    </span>
+                </a>
+            </div>
+        </div>
+        <div class="card-body">
+            <Table TableItem="SharedDomain" Items="SharedDomains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
+                <Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
+                <Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
+                <Column TableItem="SharedDomain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
+                    <Template>
+                        <DeleteButton Confirm="true"
+                                      AdditionalCssClasses="float-end"
+                                      OnClick="() => Delete(context)">
+                        </DeleteButton>
+                    </Template>
+                </Column>
+            </Table>
+        </div>
+    </div>
+</LazyLoader>
 
 
 @code
 @code
 {
 {
@@ -72,12 +79,12 @@
         }
         }
         catch (Exception e)
         catch (Exception e)
         {
         {
-            //TODO: Add check if any domains are left
-            
+    //TODO: Add check if any domains are left
+
             await AlertService.Error(
             await AlertService.Error(
                 SmartTranslateService.Translate("Error"),
                 SmartTranslateService.Translate("Error"),
                 SmartTranslateService.Translate("Something went wrong. Are any domains associated with this shared domain still there?")
                 SmartTranslateService.Translate("Something went wrong. Are any domains associated with this shared domain still there?")
-            );
+                );
         }
         }
     }
     }
 }
 }

+ 109 - 33
Moonlight/Shared/Views/Admin/Security/Malware.razor

@@ -4,12 +4,23 @@
 @using Moonlight.App.Services.Background
 @using Moonlight.App.Services.Background
 @using Moonlight.App.Services
 @using Moonlight.App.Services
 @using BlazorTable
 @using BlazorTable
+@using Microsoft.EntityFrameworkCore
+@using Moonlight.App.ApiClients.Wings
 @using Moonlight.App.Database.Entities
 @using Moonlight.App.Database.Entities
 @using Moonlight.App.Events
 @using Moonlight.App.Events
+@using Moonlight.App.Helpers
 @using Moonlight.App.Models.Misc
 @using Moonlight.App.Models.Misc
+@using Moonlight.App.Repositories
+@using Moonlight.App.Services.Interop
+@using Moonlight.App.Services.Sessions
 
 
 @inject MalwareBackgroundScanService MalwareBackgroundScanService
 @inject MalwareBackgroundScanService MalwareBackgroundScanService
 @inject SmartTranslateService SmartTranslateService
 @inject SmartTranslateService SmartTranslateService
+@inject ServerService ServerService
+@inject ToastService ToastService
+@inject SessionServerService SessionServerService
+@inject Repository<Server> ServerRepository
+@inject Repository<User> UserRepository
 @inject EventSystem Event
 @inject EventSystem Event
 
 
 @implements IDisposable
 @implements IDisposable
@@ -38,7 +49,7 @@
                 }
                 }
                 else
                 else
                 {
                 {
-                    <div class="mb-3">
+                    <div class="mb-5">
                         <div class="form-check">
                         <div class="form-check">
                             <input class="form-check-input" type="checkbox" id="scanAllServers" @bind="MalwareBackgroundScanService.ScanAllServers">
                             <input class="form-check-input" type="checkbox" id="scanAllServers" @bind="MalwareBackgroundScanService.ScanAllServers">
                             <label class="form-check-label" for="scanAllServers">
                             <label class="form-check-label" for="scanAllServers">
@@ -48,8 +59,13 @@
                     </div>
                     </div>
 
 
                     <WButton Text="@(SmartTranslateService.Translate("Start scan"))"
                     <WButton Text="@(SmartTranslateService.Translate("Start scan"))"
-                             CssClasses="btn-success"
-                             OnClick="MalwareBackgroundScanService.Start">
+                             CssClasses="btn-success me-3"
+                             OnClick="Scan">
+                    </WButton>
+                    
+                    <WButton Text="@(SmartTranslateService.Translate("Purge page"))"
+                             CssClasses="btn-danger"
+                             OnClick="PurgeSelected">
                     </WButton>
                     </WButton>
                 }
                 }
             </div>
             </div>
@@ -65,39 +81,14 @@
             <div class="card-body">
             <div class="card-body">
                 <LazyLoader @ref="LazyLoaderResults" Load="LoadResults">
                 <LazyLoader @ref="LazyLoaderResults" Load="LoadResults">
                     <div class="table-responsive">
                     <div class="table-responsive">
-                        <Table TableItem="Server" Items="ScanResults.Keys" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
+                        <Table @ref="Table" TableItem="Server" Items="ScanResults.Keys" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
                             <Column TableItem="Server" Title="@(SmartTranslateService.Translate("Server"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
                             <Column TableItem="Server" Title="@(SmartTranslateService.Translate("Server"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
                                 <Template>
                                 <Template>
                                     <a href="/server/@(context.Uuid)">@(context.Name)</a>
                                     <a href="/server/@(context.Uuid)">@(context.Name)</a>
                                 </Template>
                                 </Template>
                             </Column>
                             </Column>
-                            <Column TableItem="Server" Title="@(SmartTranslateService.Translate("Results"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
-                                <Template>
-                                    <div class="row">
-                                        @foreach (var result in ScanResults[context])
-                                        {
-                                            <div class="col-12 col-md-6 p-3">
-                                                <div class="accordion" id="scanResult@(result.GetHashCode())">
-                                                    <div class="accordion-item">
-                                                        <h2 class="accordion-header" id="scanResult-header@(result.GetHashCode())">
-                                                            <button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#scanResult-body@(result.GetHashCode())" aria-expanded="false" aria-controls="scanResult-body@(result.GetHashCode())">
-                                                                <span>@(result.Title)</span>
-                                                            </button>
-                                                        </h2>
-                                                        <div id="scanResult-body@(result.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="scanResult-header@(result.GetHashCode())" data-bs-parent="#scanResult">
-                                                            <div class="accordion-body">
-                                                                <p>
-                                                                    @(result.Description)
-                                                                </p>
-                                                            </div>
-                                                        </div>
-                                                    </div>
-                                                </div>
-                                            </div>
-                                        }
-                                    </div>
-                                </Template>
-                            </Column>
+                            <Column TableItem="Server" Title="@(SmartTranslateService.Translate("Title"))" Field="@(x => ScanResults[x].Title)" Sortable="false" Filterable="true" />
+                            <Column TableItem="Server" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => ScanResults[x].Description)" Sortable="false" Filterable="true" />
                             <Pager ShowPageNumber="true" ShowTotalCount="true"/>
                             <Pager ShowPageNumber="true" ShowTotalCount="true"/>
                         </Table>
                         </Table>
                     </div>
                     </div>
@@ -109,7 +100,9 @@
 
 
 @code
 @code
 {
 {
-    private readonly Dictionary<Server, MalwareScanResult[]> ScanResults = new();
+    private readonly Dictionary<Server, MalwareScanResult> ScanResults = new();
+
+    private Table<Server> Table;
 
 
     private LazyLoader LazyLoaderResults;
     private LazyLoader LazyLoaderResults;
 
 
@@ -117,7 +110,18 @@
     {
     {
         await Event.On<Object>("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); });
         await Event.On<Object>("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); });
 
 
-        await Event.On<Object>("malwareScan.result", this, async o => { await LazyLoaderResults.Reload(); });
+        await Event.On<Server?>("malwareScan.result", this, async server =>
+        {
+            lock (MalwareBackgroundScanService.ScanResults)
+            {
+                if (server == null)
+                    return;
+                
+                ScanResults.Add(server, MalwareBackgroundScanService.ScanResults[server]);
+            }
+            
+            await InvokeAsync(StateHasChanged);
+        });
     }
     }
 
 
     private Task LoadResults(LazyLoader arg)
     private Task LoadResults(LazyLoader arg)
@@ -140,4 +144,76 @@
         await Event.Off("malwareScan.status", this);
         await Event.Off("malwareScan.status", this);
         await Event.Off("malwareScan.result", this);
         await Event.Off("malwareScan.result", this);
     }
     }
+
+    private async Task PurgeSelected()
+    {
+        int users = 0;
+        int servers = 0;
+        
+        int allServersCount = Table.FilteredItems.Count();
+        int position = 0;
+
+        await ToastService.CreateProcessToast("purgeProcess", "Purging");
+        
+        foreach (var item in Table.FilteredItems)
+        {
+            position++;
+            
+            if (item == null)
+                continue;
+
+            try
+            {
+                var server = ServerRepository.Get()
+                    .Include(x => x.Owner)
+                    .FirstOrDefault(x => x.Id == item.Id);
+                
+                if(server == null)
+                    continue;
+
+                await ToastService.UpdateProcessToast("purgeProcess", $"[{position}/{allServersCount}] {server.Name}");
+
+                ScanResults.Remove(item);
+
+                await InvokeAsync(StateHasChanged);
+
+                // Owner
+                
+                server.Owner.Status = UserStatus.Banned;
+                UserRepository.Update(server.Owner);
+                users++;
+
+                try
+                {
+                    await SessionServerService.ReloadUserSessions(server.Owner);
+                }
+                catch (Exception) {/* Ignored */}
+
+    // Server itself
+
+                await ServerService.SetPowerState(server, PowerSignal.Kill);
+                await ServerService.Delete(server);
+                servers++;
+            }
+            catch (Exception e)
+            {
+                Logger.Warn($"Error purging server: {item.Uuid}");
+                Logger.Warn(e);
+
+                await ToastService.Error(
+                    $"Failed to purge server '{item.Name}': {e.Message}"
+                );
+            }
+        }
+        
+        await ToastService.RemoveProcessToast("purgeProcess");
+        await ToastService.Success($"Successfully purged {servers} servers by {users} users");
+    }
+
+    private async Task Scan()
+    {
+        ScanResults.Clear();
+        await InvokeAsync(StateHasChanged);
+        await MalwareBackgroundScanService.Start();
+    }
 }
 }

+ 2 - 2
Moonlight/Shared/Views/Admin/Servers/New.razor

@@ -229,7 +229,7 @@
     {
     {
         try
         try
         {
         {
-            await ServerService.Create(Model.Name, Model.Cpu, Model.Memory, Model.Disk, Model.Owner, Model.Image, Model.Node, server =>
+            var newServer = await ServerService.Create(Model.Name, Model.Cpu, Model.Memory, Model.Disk, Model.Owner, Model.Image, Model.Node, server =>
             {
             {
                 server.OverrideStartup = Model.OverrideStartup;
                 server.OverrideStartup = Model.OverrideStartup;
                 server.DockerImageIndex = Model.DockerImageIndex;
                 server.DockerImageIndex = Model.DockerImageIndex;
@@ -242,7 +242,7 @@
             });
             });
 
 
             await ToastService.Success(SmartTranslateService.Translate("Server successfully created"));
             await ToastService.Success(SmartTranslateService.Translate("Server successfully created"));
-            NavigationManager.NavigateTo("/admin/servers");
+            NavigationManager.NavigateTo($"/server/{newServer.Uuid}");
         }
         }
         catch (DisplayException e)
         catch (DisplayException e)
         {
         {

+ 1 - 1
Moonlight/Shared/Views/Server/Index.razor

@@ -190,7 +190,7 @@
             .Include(x => x.Variables)
             .Include(x => x.Variables)
             .Include(x => x.MainAllocation)
             .Include(x => x.MainAllocation)
             .Include(x => x.Owner)
             .Include(x => x.Owner)
-            .First(x => x.Uuid == uuid);
+            .FirstOrDefault(x => x.Uuid == uuid);
 
 
         if (CurrentServer != null)
         if (CurrentServer != null)
         {
         {

+ 0 - 1
Moonlight/defaultstorage/configs/default_subscription.json

@@ -1 +0,0 @@
-[]

+ 0 - 778
Moonlight/defaultstorage/resources/lang/de_de.lang

@@ -1,778 +0,0 @@
-Open support;Support Öffnen
-About us;Über uns
-Imprint;Impressum
-Privacy;Privacy
-Login;Einloggen
-Register;Registrieren
-Insert brand name...;Firmenname eingeben...
-Save and continue;Speichern und Fortfahren
-Saving;Speichern
-Configure basics;Grundlagen einstellen
-Brand name;Firmenname
-test;test
-Insert first name...;Vornamen eingeben...
-Insert last name...;Nachnamen eingeben...
-Insert email address...;E-Mail-Adresse eingeben...
-Add;Hinzufügen
-Adding...;Wird hinzugefügt...
-Add admin accounts;Admin Konto hinzufügen
-First name;Vorname
-Last name;Nachname
-Email address;E-Mail-Adresse
-Enter password;Password eingeben
-Next;Weiter
-Back;Zurück
-Configure features;Features konfigurieren
-Support chat;Chat Hilfe
-Finish;Fertigstellen
-Finalize installation;Installation Fertigstellen
-Moonlight basic settings successfully configured;Moonlight's Standard-Einstellungen wurden konfiguriert
-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. Bitte versuche, sie neu zu laden.
-Setup complete;Einrichtung abgeschlossen
-It looks like this moonlight instance is ready to go;Diese Moonlight Instanz ist bereit
-User successfully created;Benutzer erfolgreich erstellt
-Ooops. Your moonlight client is crashed;Ups. Dein Moonlight ist abgestürzt.
-This error has been reported to the moonlight team;Dieser Fehler wurde dem Moonlight-Team mitgeteilt
-Sign In;Anmelden
-Sign in to start with moonlight;Anmelden, um mit Moonlight zu starten
-Sign in with Discord;Mit Discord Anmelden
-Or with email;Oder mit E-Mail
-Forgot password?;Password vergessen?
-Sign-in;Anmelden
-Not registered yet?;Noch nicht Registriert?
-Sign up;Registrieren
-Authenticating;Authentifizieren...
-Sign in with Google;Mit Google Anmelden
-Working;Bitte warten...
-Error;Fehler
-Email and password combination not found;E-Mail und Password-Kombination wurden nicht gefunden
-Email;E-mail
-Password;Password
-Account settings;Benutzer-Einstellungen
-Logout;Abmelden
-Dashboard;Dashboard
-Order;Bestellen
-Website;Webseite
-Database;Datenbank
-Domain;Domain
-Servers;Server
-Websites;Webseiten
-Databases;Datenbanken
-Domains;Domains
-Changelog;Änderungen
-Firstname;Vorname
-Lastname;Nachname
-Repeat password;Password wiederholen
-Sign Up;Anmelden
-Sign up to start with moonlight;Registrieren um mit Moonlight zu starten
-Sign up with Discord;Mit Discord Registrieren
-Sign up with Google;Mit Google Registrieren
-Sign-up;Registrieren
-Already registered?;Schon Registriert?
-Sign in;Registrieren
-Create something new;Etwas neues erstellen
-Create a gameserver;Einen Gameserver erstellen
-A new gameserver in just a few minutes;Ein neuer Gameserver in wenigen Minuten
-Create a database;Eine Datenbank erstellen
-A quick way to store your data and manage it from all around the world;Eine schnelle Möglichkeit, um deine Daten von überall auf der Welt zu verwalten
-Manage your services;Deine Dienste verwalten
-Manage your gameservers;Gameserver verwalten
-Adjust your gameservers;Deine Gameserver anpassen
-Manage your databases;Datenbanken verwalten
-Insert, delete and update the data in your databases;Daten in die Datenbank einfügen, entfernen und ändern
-Create a website;Eine Webseite erstellen
-Make your own websites with a webspace;Mit einem Webspace eine Webseite erstellen
-Create a domain;Eine Domain erstellen
-Make your servvices accessible throught your own domain;Mache deine Dienste mit einer Domain erreichbar
-Manage your websites;Deine Webseiten verwalten
-Modify the content of your websites;Den Inhalt deiner Webseiten verwalten
-Manage your domains;Deine Domains verwalten
-Add, edit and delete dns records;DNS-Records hinzufügen, entfernen oder bearbeiten
-Admin;Admin
-System;System
-Overview;Übersicht
-Manager;Manager
-Cleanup;Cleanup
-Nodes;Nodes
-Images;Images
-aaPanel;aaPanel
-Users;Benutzer
-Support;Hilfe
-Statistics;Statistiken
-No nodes found. Start with adding a new node;Keine Nodes gefunden. Ein neues Node hinzufügen
-Nodename;Nodename
-FQDN;FQDN
-Create;Erstellen
-Creating;Wird erstellt...
-Http port;Http Port
-Sftp port;Sftp Port
-Moonlight daemon port;Moonlight Daemon Port
-SSL;SSL
-CPU Usage;CPU Auslastung
-In %;In %
-Memory;Arbeitsspeicher
-Used / Available memory;Benutzter / Verfügbarer Arbeitsspeicher
-Storage;Speicherplatz
-Available storage;Verfügbarer Speicherplatz
-Add a new node;Ein neues Node hinzufügen
-Delete;Löschen
-Deleting;Wird gelöscht...
-Edit;Bearbeiten
-Token Id;Token Id
-Token;Token
-Save;Speichern
-Setup;Aufsetzen
-Open a ssh connection to your node and enter;Eine SSH verbindung zum Node hinzufügen und öffnen
-and paste the config below. Then press STRG+O and STRG+X to save;und die Config darunter einfügern. Dann STRG+O und STRG+X um zu Speichern
-Before configuring this node, install the daemon;Installiere den Daemon bevor du dieses Node konfigurierst
-Delete this node?;Dieses Node löschen?
-Do you really want to delete this node;Möchtest du dieses Node wirklich löschen?
-Yes;Ja
-No;Nein
-Status;Status
-Adding;Hinzufügen
-Port;Port
-Id;Id
-Manage;Verwalten
-Create new server;Neuen Server erstellen
-No servers found;Keine Server gefunden
-Server name;Server Name
-Cpu cores;CPU Kerne
-Disk;Speicherplatz
-Image;Image
-Override startup;Startup überschreiben
-Docker image;Docker Image
-CPU Cores (100% = 1 Core);CPU Kerne (100% = 1 Kern)
-Server successfully created;Server erfolgreich erstellt
-Name;Name
-Cores;Kerne
-Owner;Besitzer
-Value;Wert
-An unknown error occured;Ein unbekannter Fehler ist aufgetreten
-No allocation found;Keine Zuweisung gefunden
-Identifier;Identifier
-UuidIdentifier;UuidIdentifier
-Override startup command;Startup Befehl überschreiben
-Loading;Wird geladen...
-Offline;Offline
-Connecting;Verbiden...
-Start;Start
-Restart;Neu Starten
-Stop;Stoppen
-Shared IP;Geteilte IP
-Server ID;Server ID
-Cpu;CPU
-Console;Console
-Files;Dateien
-Backups;Backups
-Network;Netzwerk
-Plugins;Plugins
-Settings;Einstellungen
-Enter command;Befehl eingeben
-Execute;Ausführen
-Checking disk space;Speicherplatz überprüfen
-Updating config files;Konfigurations-Dateien werden geupdatet
-Checking file permissions;Datei-Rechte werden überprüft
-Downloading server image;Server Image wird heruntergeladen
-Downloaded server image;Server Image wurde heruntergeladen
-Starting;Startet
-Online;Online
-Kill;Kill
-Stopping;Stoppt
-Search files and folders;Ordner und Dateien durchsuchen
-Launch WinSCP;WinSCP starten
-New folder;Neuer Ordner
-Upload;Hochladen
-File name;Datei-Name
-File size;Datei-Größe
-Last modified;Zuletz geändert
-Cancel;Abbrechen
-Canceling;Wird Abbgebrochen
-Running;Läuft
-Loading backups;Backups werden geladen
-Started backup creation;Backup wird erstellt
-Backup is going to be created;Backup wird erstellt werden
-Rename;Umbenennen
-Move;Bewegen
-Archive;Archivieren
-Unarchive;Archivieren rückgängig machen
-Download;Herunterladen
-Starting download;Herunterladen wird gestartet
-Backup successfully created;Backup wurde erfolgreich erstellt
-Restore;Wiederherstellen
-Copy url;URL Kopieren
-Backup deletion started;Backup löschung wird gestartet
-Backup successfully deleted;Backup wurde erfolgreich gelöscht
-Primary;Primärer
-This feature is currently not available;Diese Funktion ist zur Zeit nicht verfügbar
-Send;Senden
-Sending;Wird gesendet
-Welcome to the support chat. Ask your question here and we will help you;Willkommen in der Chat Hilfe. Stelle hier deine Frage und wir helfen dir.
- minutes ago; Minuten
-just now;gerade eben
-less than a minute ago;weniger als eine Minute
-1 hour ago;vor einer Stunde
-1 minute ago;vor einer Minute
-Failed;Fehlgeschlagen
- hours ago; Stunden
-Open tickets;Tickets öffnen
-Actions;Aktionen
-No support ticket is currently open;Kein Support Ticket ist zurzeit offen.
-User information;Benutzer-Information
-Close ticket;Ticket schließen
-Closing;Wird geschlossen...
-The support team has been notified. Please be patient;Das Support-Team wurde benachrichtigt. Habe etwas gedult
-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 einem Tag
-is typing;schreibt...
-are typing;schreiben
-No domains available;Keine Domains verfügbar
-Shared domains;Geteilte Domains
-Shared domain;Geteilte Domain
-Shared domain successfully deleted;Geteilte Domain wurde erfolgreich gelöscht
-Shared domain successfully added;Geteilte Domain wurde erfolgreich hinzugefügt
-Domain name;Domain Name
-DNS records for;DNS-Record für
-Fetching dns records;Es wird nach DNS-Records gesucht
-No dns records found;Keine DNS-Records gefunden
-Content;Inhalt
-Priority;Priorität
-Ttl;Ttl
-Enable cloudflare proxy;Cloudflare-Proxy benutzen
-CF Proxy;CF Proxy
- days ago; Tage
-Cancle;Abbrechen
-An unexpected error occured;Ein unbekannter Fehler ist aufgetreten
-Testy;Testy
-Error from cloudflare api;Fehler von der Cloudflare-API
-Profile;Profil
-No subscription available;Kein Abo verfügbar
-Buy;Kaufen
-Redirecting;Weiterleiten
-Apply;Anwenden
-Applying code;Code Anwenden
-Invalid subscription code;Unbekannter Abo-Code
-Cancel Subscription;Abo beenden
-Active until;Aktiv bis
-We will send you a notification upon subscription expiration;Wenn dein Abo endet, senden wir dir eine E-Mail
-This token has been already used;Dieser Token wurde schon benutzt
-New login for;Neue Anmeldung für
-No records found for this day;Für diesen Tag wurden keine Records gefunden
-Change;Ändern
-Changing;Wird geändert
-Minecraft version;Minecraft Version
-Build version;Build Version
-Server installation is currently running;Der Server wird installiert.
-Selected;Ausgewählt
-Move deleted;Gelöschtest Bewegen
-Delete selected;Ausgewähltes löschen
-Log level;Log Level
-Log message;Log Message
-Time;Zeit
-Version;Version
-You are running moonlight version;Du benutzt die Moonlight-Version
-Operating system;Betriebssystem
-Moonlight is running on;Moonlight läuft auf
-Memory usage;Arbeitsspeicher-Auslastung
-Moonlight is using;Moonlight benutzt
-of memory;des Arbeitsspeichers
-Cpu usage;CPU Auslastung
-Refresh;Neu Laden
-Send a message to all users;Eine Nachricht an alle Benutzer senden
-IP;IP
-URL;URL
-Device;Gerät
-Change url;URL Ändern
-Message;Nachricht
-Enter message;Nachricht eingeben
-Enter the message to send;Eine Nachricht zum senden eingeben
-Confirm;Bestätigen
-Are you sure?;Bist du dir sicher
-Enter url;URL eingeben
-An unknown error occured while starting backup deletion;Ein unbekannter Fehler ist während der Backuplöschung aufgetreten
-Success;erfolgreich
-Backup URL successfully copied to your clipboard;Die Backup URL wurde in deine Zwischenablage kopiert
-Backup restore started;Backup wiederherstellung gestartet
-Backup successfully restored;Das Backup wurde erfolgreich wiedergeherstellt
-Register for;Registrieren für
-Core;Kern
-Logs;Logs
-AuditLog;AuditLog
-SecurityLog;SecurityLog
-ErrorLog;ErrorLog
-Resources;Resourcen
-WinSCP cannot be launched here;WinSCP kann nicht gestartet werden
-Create a new folder;Neuen Ordner erstellen
-Enter a name;Einen Namen eingeben
-File upload complete;Dateiupload abgeschlossen
-New server;Neuer Server
-Sessions;Sitzungen
-New user;Neuer Benutzer
-Created at;Erstellt am
-Mail template not found;E-Mail template wurde nicht gefunden
-Missing admin permissions. This attempt has been logged ;Fehlende Admin-Rechte. Dieser Versuch wurde aufgezeichnet
-Address;Addresse
-City;Stadt
-State;Land
-Country;Staat
-Totp;Totp
-Discord;Discord
-Subscription;Abonnement
-None;None
-No user with this id found;Kein Benutzer mit dieser ID gefunden
-Back to list;Zurück zur liste
-New domain;Neue domain
-Reset password;Password wiederherstellen
-Password reset;Password wiederherstellung
-Reset the password of your account;Password deines Accounts zurücksetzen
-Wrong here?;Falsch hier?
-A user with this email can not be found;Ein Benutzer mit dieser E-Mail konnte nicht gefunden werden
-Password reset successfull. Check your mail;Password wiederherstellung erfolgreich. Überprüfe deine Email
-Discord bot;Discord Bot
-New image;Neues Image
-Description;Beschreibung
-Uuid;UUID
-Enter tag name;Tag Namen eingeben
-Remove;Entfernen
-No tags found;Keine Tags gefunden
-Enter docker image name;Docker Image Namen eingeben
-Tags;Tags
-Docker images;Docker Images
-Default image;Standard Image
-Startup command;Startup Befehl
-Install container;Install container
-Install entry;Install entry
-Configuration files;Konfigurationsdateien
-Startup detection;Startuperkennung
-Stop command;Stopp-Befehl
-Successfully saved image;Das Image wurde erfolgreich gespeichert
-No docker images found;Keine Docker Images gefunden
-Key;Schlüssel
-Default value;Standardwert
-Allocations;Zuweisung
-No variables found;Keine Variablen gefunden
-Successfully added image;Das Image wurde erfolgreich hinzugefügt
-Password change for;Password ändern für
-of;von
-New node;Neues Node
-Fqdn;Fqdn
-Cores used;Kerne genutzt
-used;benutzt
-5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64
-Host system information;Host System Information
-0;0
-Docker containers running;Laufende Docker Container
-details;Details
-1;1
-2;2
-DDos;DDos
-No ddos attacks found;Keine DDoS gefunden
-Node;Node
-Date;Datum
-DDos attack started;DDos Attacke gestartet
-packets;Pakete
-DDos attack stopped;DDos Attacke gestoppt
- packets; Pakete
-Stop all;Alle Stoppen
-Kill all;Allen Killen
-Network in;Network in
-Network out;Network out
-Kill all servers;Alle 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
-to;zu
-Stop all servers;Alle Server stoppen
-Do you really want to stop all running servers?;Möchtest du wirklich alle laufenden Server Killen?
-Manage ;Verwalten 
-Manage user ;Benutzer verwalten 
-Reloading;Neu Laden...
-Update;Aktualisieren
-Updating;Wird Aktualisiert...
-Successfully updated user;Benutzer erfolgreich aktualisiert
-Discord id;Discord ID
-Discord username;Discord Benutzername
-Discord discriminator;Discord Tag
-The Name field is required.;Der Name dieses Feldes ist erforderlich
-An error occured while logging you in;Ein Fehler ist aufgetreten, als du dich angemeldet hast
-You need to enter an email address;Du musst eine E-Mail-Adresse angeben
-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 Password eingeben, das mindestens 8 Buchstaben lang ist
-Proccessing;Wird verarbeitet...
-The FirstName field is required.;Das Vorname-Feld ist erforderlich
-The LastName field is required.;Das Nachname-Feld ist erforderlich
-The Address field is required.;Das Address-Feld ist erforderlich
-The City field is required.;Das Stadt-Feld ist erforderlich
-The State field is required.;Das Staat-Feld ist erforderlich
-The Country field is required.;Das Land-Feld ist erforderlich
-Street and house number requered;Straße und Hausnummer erforderlich
-Max lenght reached;Maximale Länge erreicht
-Server;Server
-stopped;gestoppt
-Cleanups;Cleanups
-executed;ausgeführt
-Used clanup;Cleanup benutzt
-Enable;Aktivieren
-Disable;Deaktivieren
-Addons;Add-ons
-Javascript version;Javascript Version
-Javascript file;Javascript Datei
-Select javascript file to execute on start;Javascript Datei zum starten auswählen
-Submit;Einreichen
-Processing;Wird verarbeitet...
-Go up;Nach oben gehen
-Running cleanup;Cleanup läuft
-servers;Servers
-Select folder to move the file(s) to;Ordner zum Bewegen der Dateien auswählen
-Paper version;Paper Version
-Join2Start;Join2Start
-Server reset;Server zurücksetzen
-Reset;Zurücksetzen
-Resetting;Wird zurückgesetzt
-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
-Resetting server;Server wird zurückgesetzt...
-Deleted file;Datei gelöscht
-Reinstalling server;Server wird reinstalliert
-Uploading files;Dateien wurden hochgeladen
-complete;vollständig
-Upload complete;Upload komplett
-Security;Sicherheit
-Subscriptions;Abonnements
-2fa Code;2FA Code
-Your account is secured with 2fa;Dein Account wird mit 2-FA gesichert
-anyone write a fancy text here?;hier einen schönen Text schreiben?
-Activate 2fa;2-FA Aktivieren
-2fa apps;2-FA Apps
-Use an app like ;Benutze eine App wie 
-or;oder
-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 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 fertig
-2fa Code requiered;2-FA Code erforderlich
-New password;Neues Password
-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.;2-FA fügt eine weitere Sicherheits-Schicht hinzu. Du musst einen 6-Ziffern-Code eingeben, um dich anzumelden.
-New subscription;Neues Abonnement
-You need to enter a name;Du musst einen Namen eingeben
-You need to enter a description;Du musst eine Beschreibung eigeben
-Add new limit;Ein neues Limit hinzufügen
-Create subscription;Abonnement erstellen
-Options;Optionen
-Amount;Betrag
-Do you really want to delete it?;Möchtes du es wirklich löschen?
-Loading your subscription;Dein Abonnement wird geladen
-Searching for deploy node;Suche Node zum Aufsetzen
-Searching for available images;Nach verfügbaren Images wird gesucht
-Server details;Server Details
-Configure your server;Deinen Server konfigurieren
-Default;Standard
-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;Prsönliche Informationen
-Enter code;Code eingeben
-Server rename;Server Umbenennen
-Create code;Code erstellen
-Save subscription;Abonnement speichern
-Enter your information;Informationen eingeben
-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;Kein Node gefunden
-No node found to deploy to found;Keine Node zum Aufsetzen gefunden
-Node offline;Node offline
-The node the server is running on is currently offline;Die Node, auf dem der Server grat läuft, ist offline
-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 konnte nicht gefunden werden
-Compress;Komprimieren
-Decompress;De-Komprimieren
-Moving;Bewegen...
-Compressing;Komprimieren...
-selected;Ausgewählt
-New website;Neue Webseite
-Plesk servers;Plesk Servers
-Base domain;Base Domain
-Plesk server;Plesk Server
-Ftp;FTP
-No SSL certificate found;Keine SSL-Zertifikate gefunden
-Ftp Host;FTP Host
-Ftp Port;FTP Port
-Ftp Username;FTP Username
-Ftp Password;FTP Password
-Use;Benutzen
-SSL Certificates;SSL Zertifikate
-SSL certificates;SSL Zertifikate
-Issue certificate;SSL-Zertifikat Ausgeben
-New plesk server;Neuer Plesk Server
-Api url;API URL
-Host system offline;Host System Offline
-The host system the website is running on is currently offline;Das Host System, auf dem diese Webseite läuft, ist offline
-No SSL certificates found;Keine SSL-Zertifikate gefunden
-No databases found for this website;Dieser Webseite konnten keine Datenbanken zugeordnet werden
-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 sollte nur Kleinbuchstaben und Zahlen enthalten
-Error from plesk;Error von Plesk
-Host;Host
-Username;Benutzername
-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 Benutzer-Feld ist erforderlich
-You need to specify a owner;Du musst einen Server-Besitzer angeben
-You need to specify a image;You need to specify a image
-Api Url;API URL
-Api Key;Api Key
-Duration;Dauer
-Enter duration of subscription;Dauer des Abonnements eingeben
-Copied code to clipboard;Code in die Zwischenablage kopiert
-Invalid or expired subscription code;Ungültiger oder Abgelaufener Abo-Code
-Current subscription;Dein Abonnement
-You need to specify a server image;Du musst ein Image angeben
-CPU;CPU
-Hour;Stunde
-Day;Tag
-Month;Monat
-Year;Jahr
-All time;Für immer
-This function is not implemented;Diese Funktion wurde noch nicht hinzugefügt
-Domain details;Domain Details
-Configure your domain;Deine Domain konfigurieren
-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 Shared-Domain angeben
-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 E-Mail-Feld ist erforderlich
-The Password field is required.;Das Password-Feld ist erforderlich
-The ConfirmPassword field is required.;Das Password-Bestätigen-Feld ist erforderlich
-Passwords need to match;Die Passwörter müssen übereinstimmen
-Cleanup exception;Cleanup ausnahme
-No shared domain found;Keine Shared-Domain gefunden
-Searching for deploy plesk server;Suchen um den Plesk Server aufzusetzen
-No plesk server found;Kein Plesk Server gefunden
-No plesk server found to deploy to;Keinen Plesk Server zum Aufsetzen gefunden
-No node found to deploy to;Kein Node zum Aufsetzen
-Website details;Details der Webseite
-Configure your website;Konfiguriere deine Webseite
-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 sollte nur aus Kleinbuchstaben bestehen
-News;Neuigkeiten
-Title...;Titel...
-Enter text...;Text einfügen...
-Saving...;Wird gespeichert...
-Deleting...;Wird gelöscht...
-Delete post;Post löschen
-Do you really want to delete the post ";Post löschen? "
-You have no domains;Du hast keine Domains
-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 Webseites
-We were not able to find any websites associated with your account;Wir haben keine Webseiten, die mit deinem Account verbunden sind, gefunden
-Guest;Gast
-You need a domain;Du brauchts eine Domain
-New post;Neuer Post
-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-Hochladen 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 Webseiten 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 aktualisieren Sie die Seite
-Verifying token, loading user data;Token verifizieren, Benutzer-Daten laden
-Reload config;Konfiguration neu laden
-Successfully reloading configuration;Konfiguration wird neu geladen...
-Successfully reloaded configuration;Die Konfiguration wurde erfolgreich neu geladen
-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 Bewertun
-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;Bewretung gespeichert
-Group;Gruppe
-Beta;Beta
-Create a new group;Eine neue Gruppe erstellen
-Download WinSCP;WinSCP herunterladen
-Show connection details;Verbindungsdetails anzeigen
-New;Neu
-New file;Neue Datei
-Connection details;Verbindungsdetails
-Malware;Malware
-Create a new file;Eine neue Datei erstellen
-Edit layout;Layout anpassen
-Uptime;Laufzeit
-Moonlight is online since;Moonlight ist online seit
-User;Benutzer
-Databases;Datenbanken
-Sitzungen;Sitzungen
-Active users;Aktive Nutzer
-Search for plugins;Nach Plugins suchen
-Search;Suchen
-Searching;Wird gesucht...
-Successfully installed gunshell;Gunshell wurde erfolgreich installiert
-Successfully installed fastasyncworldedit;Fastasyncworldedit wurde erfolgreich installiert
-Successfully installed minimotd;Minimotd wurde erfolgreich installiert
-Moonlight health;Moonlight Status
-Healthy;Healthy
-Successfully saved file;Die Datei wurde erfolgreich gespeichert
-Unsorted servers;Unsortierte Server
-Enter a new name;Einen neuen Namen eingeben
-Sign in with;Anmelden mit
-Make your services accessible through your own domain;Mache deine Dienste mit einer Domain verfügbar
-New group;Neue Gruppe
-Finish editing layout;Layout Fertigstellen
-Remove group;Gruppe löschen
-Hidden in edit mode;Im Edit-Mode ausgeblendet
-Enter your 2fa code here;Deinen 2FA Code hier eingeben
-Two factor authentication;Zwei Faktor Authentifikation
-Preferences;Preferenzen
-Streamer mode;Streamer Modus
-Scan the QR code and enter the code generated by the app you have scanned it in;Scanne den QR-Code mit der App
-Start scan;Scan Starten
-Results;Ergebnisse
-Scan in progress;Wird gescannt
-Debug;Debug
-Save changes;Änderungen speichern
-Delete domain;Domain löschen
-Python version;Python Version
-Python file;Python Datei
-Select python file to execute on start;Wähle die Python Datei aus, die beim Start ausgeführt wird
-You have no webspaces;Du hast keine Webspaces
-We were not able to find any webspaces associated with your account;Wir haben keine Webspaces gefunden, die mit deinem Account verbunden sind
-Backup download successfully started;Der Backup-Download wurde erfolgreich gestartet
-Error from cloud panel;Fehler vom Cloud Panel
-Error from wings;Fehler von Wings
-Remove link;Link entfernen
-Your account is linked to a discord account;Dein Account ist mit einem Discord Account verbunden
-You are able to use features like the discord bot of moonlight;Du kannst Features wie den Discord Bot von Moonlight benutzen
-The password should be at least 8 characters long;Dein Password sollte mindestens 8 Zeichen lang sein
-The name should only consist of lower case characters or numbers;Der Name sollte nur aus Kleinbuchstaben und Nummern bestehen
-The requested resource was not found;Die gewünschte Ressource wurde nicht gefunden
-We were not able to find the requested resource. This can have following reasons;Wir haben die gewünschte Ressource nicht gefunden. Das kann folgenden Grund haben
-The resource was deleted;Die Ressource wurde gelöscht
-You have to permission to access this resource;Du hast keine Rechte um auf diese Ressource zuzugreifen
-You may have entered invalid data;Du hast ungültige Daten angegeben
-A unknown bug occured;Ein unbekannter Bug ist aufgetreten
-An api was down and not proper handled;Eine API war offline und wurde nicht richtig behandelt
-A database with this name does already exist;Es gibt bereits eine Datenbank mit diesem Namen
-Successfully installed quickshop-hikari;Quickshop-Hikari wurde erfolgreich installiert
-You need to enter a valid domain;Du musst eine gültige Domain angeben
-2fa code;2FA Code
-This feature is not available for;Dieses Feature ist nicht verfügbar für
-Your account is currently not linked to discord;Dein Account ist nicht mit Discord verbunden
-To use features like the discord bot, link your moonlight account with your discord account;Um Features wie Moonlights Discord Bot nutzen zu können, verbinde deinen Account mit Discord
-Link account;Account verbinden
-Continue;Weiter
-Preparing;Wird vorbereitet
-Make sure you have installed one of the following apps on your smartphone and press continue;Stelle sicher dass du eine der folgenden Apps auf deinem Smartphone installiert hast und drücke auf Weiter
-The max lenght for the name is 32 characters;Die maximale Länge für den Namen beträgt 32 Zeichen
-Successfully installed chunky;Chunky wurde erfolgreich installiert
-Successfully installed huskhomes;Huskhomes wurde erfolgreich installiert
-Successfully installed simply-farming;Simple-Farming wurde erfolgreich installiert
-You need to specify a first name;Du musst einen Vornamen eingeben
-You need to specify a last name;Du musst einen Nachnamen angeben
-You need to specify a password;Du musst ein Password angeben
-Please solve the captcha;Bitte löse das Captcha
-The email is already in use;Diese Email wird bereits verwendet
-The dns records of your webspace do not point to the host system;Die DNS Records deines Webspaces zeigen nicht zum Hostsystem
-Scan complete;Scan komplett
-Currently scanning:;Zurzeit wird gescannt:
-Successfully installed dynmap;Dynmap wurde erfolgreich installiert
-Successfully installed squaremap;Squaremap wurde erfolgreich installiert
-No web host found;Kein Webhost gefunden
-No web host found to deploy to;Es wurde kein Webhost zum Aufsetzen gefunden
-Successfully installed sleeper;Sleeper wurde erfolgreich installiert
-You need to enter a domain;Du musst eine Domain angeben
-Enter a ip;Eine IP angeben
-Ip Bans;Ip Bans
-Ip;Ip
-Successfully installed simple-voice-chat;Simple-Voice-Chat wurde erfolgreich installiert
-Successfully installed smithing-table-fix;Smithing-Table-Fix wurde erfolgreich installiert
-Successfully installed justplayer-tpa;Justplayer-TPA wurde erfolgreich installiert
-Successfully installed ishop;iShop wurde erfolgreich installiert
-Successfully installed lifestealre;Lifestealre wurde erfolgreich installiert
-Successfully installed lifeswap;Lifeswap wurde erfolgreich installiert
-Java version;Java Version
-Jar file;JAR Datei
-Select jar to execute on start;Eine JAR Datei auswählen, die beim Start ausgeführt wird
-A website with this domain does already exist;Eine Webseite mit dieser Domain existiert bereits
-Successfully installed discordsrv;DiscordSrv wurde erfolgreich installiert
-Reinstall;Reinstallieren
-Reinstalling;Wird Reinstalliert
-Successfully installed freedomchat;Freedomchat wurde erfolgreich installiert
-Leave empty for the default background image;Für das Standard Hintergrundbild freilassen
-Background image url;URL zum Hintergrundbild
-of CPU used;von der CPU wird benutzt
-memory used;Speicher benutzt
-163;163
-172;172
-Sentry;Sentry
-Sentry is enabled;Sentry ist eingeschaltet
-Successfully installed mobis-homes;Mobis-Homes wurde erfolgreich installiert
-Your moonlight account is disabled;Dein Moonlight Account ist deaktiviert
-Your moonlight account is currently disabled. But dont worry your data is still saved;Dein Moonlight Account ist deaktiviert, aber keine Sorge, deine Daten sind noch gespeichert
-You need to specify a email address;Du musst eine Email Adresse angeben
-A user with that email does already exist;Ein Nutzer mit dieser Email Adresse existiert bereits
-Successfully installed buildmode;Buildmode wurde erfolgreich installiert
-Successfully installed plasmo-voice;Plasmo-Voice wurde erfolgreich installiert
-157;157
-174;174
-158;158
-Webspace not found;Webspace nicht gefunden
-A webspace with that id cannot be found or you have no access for this webspace;Ein Webspace mit dieser ID wurde nicht gefunden, oder du hast keinen Zugriff auf diesen Webspace
-No plugin download for your minecraft version found;Kein Plugin für deine Minecraft-Version gefunden
-Successfully installed gamemode-alias;Gamemode-Alias wurde erfolgreich installiert
-228;228
-User;Nutzer
-Send notification;Benachrichtigung senden
-Successfully saved changes;Änderungen erfolgreich gespeichert
-Archiving;Wird Archiviert
-Server is currently not archived;Dieser Server ist zurzeit nicht Archiviert
-Add allocation;Zuweisung hinzufügen
-231;231
-175;175
-Dotnet version;Dotnet Version
-Dll file;DLL Datei
-Select dll to execute on start;Wähle die DLL Datei aus, die beim Start ausgeführt wird 

+ 0 - 567
Moonlight/defaultstorage/resources/lang/en_us.lang

@@ -1,567 +0,0 @@
-Open support;Open support
-About us;About us
-Imprint;Imprint
-Privacy;Privacy
-Login;Login
-Register;Register
-Insert brand name...;Insert brand name...
-Save and continue;Save and continue
-Saving;Saving
-Configure basics;Configure basics
-Brand name;Brand name
-test;test
-Insert first name...;Insert first name...
-Insert last name...;Insert last name...
-Insert email address...;Insert email address...
-Add;Add
-Adding...;Adding...
-Add admin accounts;Add admin accounts
-First name;First name
-Last name;Last name
-Email address;Email address
-Enter password;Enter password
-Next;Next
-Back;Back
-Configure features;Configure features
-Support chat;Support chat
-Finish;Finish
-Finalize installation;Finalize installation
-Moonlight basic settings successfully configured;Moonlight basic settings successfully configured
-Ooops. This page is crashed;Ooops. This page crashed
-This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page;This page crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page
-Setup complete;Setup complete
-It looks like this moonlight instance is ready to go;It looks like this moonlight instance is ready to go
-User successfully created;User successfully created
-Ooops. Your moonlight client is crashed;Ooops. Your moonlight client crashed
-This error has been reported to the moonlight team;This error has been reported to the moonlight team
-Sign In;Sign In
-Sign in to start with moonlight;Sign in to start with moonlight
-Sign in with Discord;Sign in with Discord
-Or with email;Or with email
-Forgot password?;Forgot password?
-Sign-in;Sign-in
-Not registered yet?;Not registered yet?
-Sign up;Sign up
-Authenticating;Authenticating
-Sign in with Google;Sign in with Google
-Working;Working
-Error;Error
-Email and password combination not found;Email and password combination not found
-Email;Email
-Password;Password
-Account settings;Account settings
-Logout;Logout
-Dashboard;Dashboard
-Order;Order
-Website;Website
-Database;Database
-Domain;Domain
-Servers;Servers
-Websites;Websites
-Databases;Databases
-Domains;Domains
-Changelog;Changelog
-Firstname;First Name
-Lastname;Last Name
-Repeat password;Repeat password
-Sign Up;Sign Up
-Sign up to start with moonlight;Sign up to start with moonlight
-Sign up with Discord;Sign up with Discord
-Sign up with Google;Sign up with Google
-Sign-up;Sign-up
-Already registered?;Already registered?
-Sign in;Sign in
-Create something new;Create something new
-Create a gameserver;Create a gameserver
-A new gameserver in just a few minutes;A new gameserver in just a few minutes
-Create a database;Create a database
-A quick way to store your data and manage it from all around the world;A quick way to store your data and manage it from all around the world
-Manage your services;Manage your services
-Manage your gameservers;Manage your gameservers
-Adjust your gameservers;Adjust your gameservers
-Manage your databases;Manage your databases
-Insert, delete and update the data in your databases;Insert, delete and update the data in your databases
-Create a website;Create a website
-Make your own websites with a webspace;Make your own websites with a webspace
-Create a domain;Create a domain
-Make your servvices accessible throught your own domain;Make your services accessible throught your own domain
-Manage your websites;Manage your websites
-Modify the content of your websites;Modify the content of your websites
-Manage your domains;Manage your domains
-Add, edit and delete dns records;Add, edit and delete dns records
-Admin;Admin
-System;System
-Overview;Overview
-Manager;Manager
-Cleanup;Cleanup
-Nodes;Nodes
-Images;Images
-aaPanel;aaPanel
-Users;Users
-Support;Support
-Statistics;Statistics
-No nodes found. Start with adding a new node;No nodes found. Start with adding a new node
-Nodename;Nodename
-FQDN;FQDN
-Create;Create
-Creating;Creating
-Http port;Http port
-Sftp port;Sftp port
-Moonlight daemon port;Moonlight daemon port
-SSL;SSL
-CPU Usage;CPU Usage
-In %;In %
-Memory;Memory
-Used / Available memory;Used / Available memory
-Storage;Storage
-Available storage;Available storage
-Add a new node;Add a new node
-Delete;Delete
-Deleting;Deleting
-Edit;Edit
-Token Id;Token Id
-Token;Token
-Save;Save
-Setup;Setup
-Open a ssh connection to your node and enter;Open a ssh connection to your node and enter
-and paste the config below. Then press STRG+O and STRG+X to save;and paste the config below. Then press STRG+O and STRG+X to save
-Before configuring this node, install the daemon;Before configuring this node, install the daemon
-Delete this node?;Delete this node?
-Do you really want to delete this node;Do you really want to delete this node
-Yes;Yes
-No;No
-Status;Status
-Adding;Adding
-Port;Port
-Id;Id
-Manage;Manage
-Create new server;Create new server
-No servers found;No servers found
-Server name;Server name
-Cpu cores;Cpu cores
-Disk;Disk
-Image;Image
-Override startup;Override startup
-Docker image;Docker image
-CPU Cores (100% = 1 Core);CPU Cores (100% = 1 Core)
-Server successfully created;Server successfully created
-Name;Name
-Cores;Cores
-Owner;Owner
-Value;Value
-An unknown error occured;An unknown error occured
-No allocation found;No allocation found
-Identifier;Identifier
-UuidIdentifier;UuidIdentifier
-Override startup command;Override startup command
-Loading;Loading
-Offline;Offline
-Connecting;Connecting
-Start;Start
-Restart;Restart
-Stop;Stop
-Shared IP;Shared IP
-Server ID;Server ID
-Cpu;Cpu
-Console;Console
-Files;Files
-Backups;Backups
-Network;Network
-Plugins;Plugins
-Settings;Settings
-Enter command;Enter command
-Execute;Execute
-Checking disk space;Checking disk space
-Updating config files;Updating config files
-Checking file permissions;Checking file permissions
-Downloading server image;Downloading server image
-Downloaded server image;Downloaded server image
-Starting;Starting
-Online;Online
-Kill;Kill
-Stopping;Stopping
-Search files and folders;Search files and folders
-Launch WinSCP;Launch WinSCP
-New folder;New folder
-Upload;Upload
-File name;File name
-File size;File size
-Last modified;Last modified
-Cancel;Cancel
-Canceling;Canceling
-Running;Running
-Loading backups;Loading backups
-Started backup creation;Started backup creation
-Backup is going to be created;Backup is going to be created
-Rename;Rename
-Move;Move
-Archive;Archive
-Unarchive;Unarchive
-Download;Download
-Starting download;Starting download
-Backup successfully created;Backup successfully created
-Restore;Restore
-Copy url;Copy url
-Backup deletion started;Backup deletion started
-Backup successfully deleted;Backup successfully deleted
-Primary;Primary
-This feature is currently not available;This feature is currently not available
-Send;Send
-Sending;Sending
-Welcome to the support chat. Ask your question here and we will help you;Welcome to the support chat. Ask your question here and we will help you
- minutes ago; minutes ago
-just now;just now
-less than a minute ago;less than a minute ago
-1 hour ago;1 hour ago
-1 minute ago;1 minute ago
-Failed;Failed
- hours ago; hours ago
-Open tickets;Open tickets
-Actions;Actions
-No support ticket is currently open;No support ticket is currently open
-User information;User information
-Close ticket;Close ticket
-Closing;Closing
-The support team has been notified. Please be patient;The support team has been notified. Please be patient
-The ticket is now closed. Type a message to open it again;The ticket is now closed. Type a message to open it again
-1 day ago;1 day ago
-is typing;is typing
-are typing;are typing
-No domains available;No domains available
-Shared domains;Shared domains
-Shared domain;Shared domain
-Shared domain successfully deleted;Shared domain successfully deleted
-Shared domain successfully added;Shared domain successfully added
-Domain name;Domain name
-DNS records for;DNS records for 
-Fetching dns records;Fetching dns records
-No dns records found;No dns records found
-Content;Content
-Priority;Priority
-Ttl;Ttl
-Enable cloudflare proxy;Enable cloudflare proxy
-CF Proxy;CF Proxy
- days ago; days ago
-Cancle;Cancle
-An unexpected error occured;An unexpected error occured
-Testy;Testy
-Error from cloudflare api;Error from cloudflare api
-Profile;Profile
-No subscription available;No subscription available
-Buy;Buy
-Redirecting;Redirecting
-Apply;Apply
-Applying code;Applying code
-Invalid subscription code;Invalid subscription code
-Cancel Subscription;Cancel Subscription
-Active until;Active until
-We will send you a notification upon subscription expiration;We will send you a notification upon subscription expiration
-This token has been already used;This token has been already used
-New login for;New login for
-No records found for this day;No records found for this day
-Change;Change
-Changing;Changing
-Minecraft version;Minecraft version
-Build version;Build version
-Server installation is currently running;Server installation is currently running
-Selected;Selected
-Move deleted;Move deleted
-Delete selected;Delete selected
-Log level;Log level
-Log message;Log message
-Time;Time
-Version;Version
-You are running moonlight version;You are running moonlight version
-Operating system;Operating system
-Moonlight is running on;Moonlight is running on
-Memory usage;Memory usage
-Moonlight is using;Moonlight is using
-of memory;of memory
-Cpu usage;Cpu usage
-Refresh;Refresh
-Send a message to all users;Send a message to all users
-IP;IP
-URL;URL
-Device;Device
-Change url;Change url
-Message;Message
-Enter message;Enter message
-Enter the message to send;Enter the message to send
-Confirm;Confirm
-Are you sure?;Are you sure?
-Enter url;Enter url
-An unknown error occured while starting backup deletion;An unknown error occured while starting backup deletion
-Success;Success
-Backup URL successfully copied to your clipboard;Backup URL successfully copied to your clipboard
-Backup restore started;Backup restore started
-Backup successfully restored;Backup successfully restored
-Register for;Register for
-Core;Core
-Logs;Logs
-AuditLog;AuditLog
-SecurityLog;SecurityLog
-ErrorLog;ErrorLog
-Resources;Resources
-WinSCP cannot be launched here;WinSCP cannot be launched here
-Create a new folder;Create a new folder
-Enter a name;Enter a name
-File upload complete;File upload complete
-New server;New server
-Sessions;Sessions
-New user;New user
-Created at;Created at
-Mail template not found;Mail template not found
-Missing admin permissions. This attempt has been logged ;)
-Address;Address
-City;City
-State;State
-Country;Country
-Totp;Totp
-Discord;Discord
-Subscription;Subscription
-None;None
-No user with this id found;No user with this id found
-Back to list;Back to list
-New domain;New domain
-Reset password;Reset password
-Password reset;Password reset
-Reset the password of your account;Reset the password of your account
-Wrong here?;Wrong here?
-A user with this email can not be found;A user with this email can not be found
-Passwort reset successfull. Check your mail;Passwort reset successfull. Check your mail
-Discord bot;Discord bot
-New image;New image
-Description;Description
-Uuid;Uuid
-Enter tag name;Enter tag name
-Remove;Remove
-No tags found;No tags found
-Enter docker image name;Enter docker image name
-Tags;Tags
-Docker images;Docker images
-Default image;Default image
-Startup command;Startup command
-Install container;Install container
-Install entry;Install entry
-Configuration files;Configuration files
-Startup detection;Startup detection
-Stop command;Stop command
-Successfully saved image;Successfully saved image
-No docker images found;No docker images found
-Key;Key
-Default value;Default value
-Allocations;Allocations
-No variables found;No variables found
-Successfully added image;Successfully added image
-Password change for;Password change for
-of;of
-New node;New node
-Fqdn;Fqdn
-Cores used;Cores used
-used;used
-5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64
-Host system information;Host system information
-0;0
-Docker containers running;Docker containers running
-details;details
-1;1
-2;2
-DDos;DDos
-No ddos attacks found;No ddos attacks found
-Node;Node
-Date;Date
-DDos attack started;DDos attack started
-packets;packets
-DDos attack stopped;DDos attack stopped
- packets; packets
-Stop all;Stop all
-Kill all;Kill all
-Network in;Network in
-Network out;Network out
-Kill all servers;Kill all servers
-Do you really want to kill all running servers?;Do you really want to kill all running servers?
-Change power state for;Change power state for
-to;to
-Stop all servers;Stop all servers
-Do you really want to stop all running servers?;Do you really want to stop all running servers?
-Manage ;Manage 
-Manage user ;Manage user 
-Reloading;Reloading
-Update;Update
-Updating;Updating
-Successfully updated user;Successfully updated user
-Discord id;Discord id
-Discord username;Discord username
-Discord discriminator;Discord discriminator
-The Name field is required.;The Name field is required.
-An error occured while logging you in;An error occured while logging you in
-You need to enter an email address;You need to enter an email address
-You need to enter a password;You need to enter a password
-You need to enter a password with minimum 8 characters in lenght;You need to enter a password with minimum 8 characters in length
-Proccessing;Proccessing
-The FirstName field is required.;The FirstName field is required.
-The LastName field is required.;The LastName field is required.
-The Address field is required.;The Address field is required.
-The City field is required.;The City field is required.
-The State field is required.;The State field is required.
-The Country field is required.;The Country field is required.
-Street and house number requered;Street and house number requered
-Max lenght reached;Max lenght reached
-Server;Server
-stopped;stopped
-Cleanups;Cleanups
-executed;executed
-Used clanup;Used clanup
-Enable;Enable
-Disabble;Disabble
-Disable;Disable
-Addons;Addons
-Javascript version;Javascript version
-Javascript file;Javascript file
-Select javascript file to execute on start;Select javascript file to execute on start
-Submit;Submit
-Processing;Processing
-Go up;Go up
-Running cleanup;Running cleanup
-servers;servers
-Select folder to move the file(s) to;Select folder to move the file(s) to
-Paper version;Paper version
-Join2Start;Join2Start
-Server reset;Server reset
-Reset;Reset
-Resetting;Resetting
-Are you sure you want to reset this server?;Are you sure you want to reset this server?
-Are you sure? This cannot be undone;Are you sure? This cannot be undone
-Resetting server;Resetting server
-Deleted file;Deleted file
-Reinstalling server;Reinstalling server
-Uploading files;Uploading files
-complete;complete
-Upload complete;Upload complete
-Security;Security
-Subscriptions;Subscriptions
-2fa Code;2fa Code
-Your account is secured with 2fa;Your account is secured with 2fa
-anyone write a fancy text here?;anyone write a fancy text here?
-Activate 2fa;Activate 2fa
-2fa apps;2fa apps
-Use an app like ;Use an app like 
-or;or
-and scan the following QR Code;and scan the following QR Code
-If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:;If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:
-Finish activation;Finish activation
-2fa Code requiered;2fa Code requiered
-New password;New password
-Secure your account;Secure your account
-2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.;2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.
-New subscription;New subscription
-You need to enter a name;You need to enter a name
-You need to enter a description;You need to enter a description
-Add new limit;Add new limit
-Create subscription;Create subscription
-Options;Options
-Amount;Amount
-Do you really want to delete it?;Do you really want to delete it?
-Loading your subscription;Loading your subscription
-Searching for deploy node;Searching for deploy node
-Searching for available images;Searching for available images
-Server details;Server details
-Configure your server;Configure your server
-Default;Default
-You reached the maximum amount of servers for every image of your subscription;You reached the maximum amount of servers for every image of your subscription
-Personal information;Personal information
-Enter code;Enter code
-Server rename;Server rename
-Create code;Create code
-Save subscription;Save subscription
-Enter your information;Enter your information
-You need to enter your full name in order to use moonlight;You need to enter your full name in order to use moonlight
-No node found;No node found
-No node found to deploy to found;No node found to deploy to found
-Node offline;Node offline
-The node the server is running on is currently offline;The node the server is running on is currently offline
-Server not found;Server not found
-A server with that id cannot be found or you have no access for this server;A server with that id cannot be found or you don't have access for this server
-Compress;Compress
-Decompress;Decompress
-Moving;Moving
-Compressing;Compressing
-selected;selected
-New website;New website
-Plesk servers;Plesk servers
-Base domain;Base domain
-Plesk server;Plesk server
-Ftp;Ftp
-No SSL certificate found;No SSL certificate found
-Ftp Host;Ftp Host
-Ftp Port;Ftp Port
-Ftp Username;Ftp Username
-Ftp Password;Ftp Password
-Use;Use
-SSL Certificates;SSL Certificates
-SSL certificates;SSL certificates
-Issue certificate;Issue certificate
-New plesk server;New plesk server
-Api url;Api url
-Host system offline;Host system offline
-The host system the website is running on is currently offline;The host system the website is running on is currently offline
-No SSL certificates found;No SSL certificates found
-No databases found for this website;No databases found for this website
-The name should be at least 8 characters long;The name should be at least 8 characters long
-The name should only contain of lower case characters and numbers;The name should only contain of lower case characters and numbers
-Error from plesk;Error from plesk
-Host;Host
-Username;Username
-SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one;SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one
-The User field is required.;The User field is required.
-You need to specify a owner;You need to specify a owner
-You need to specify a image;You need to specify a image
-Api Url;Api Url
-Api Key;Api Key
-Duration;Duration
-Enter duration of subscription;Enter duration of subscription
-Copied code to clipboard;Copied code to clipboard
-Invalid or expired subscription code;Invalid or expired subscription code
-Current subscription;Current subscription
-You need to specify a server image;You need to specify a server image
-CPU;CPU
-Hour;Hour
-Day;Day
-Month;Month
-Year;Year
-All time;All time
-This function is not implemented;This function is not implemented
-Domain details;Domain details
-Configure your domain;Configure your domain
-You reached the maximum amount of domains in your subscription;You reached the maximum amount of domains in your subscription
-You need to specify a shared domain;You need to specify a shared domain
-A domain with this name does already exist for this shared domain;A domain with this name does already exist for this shared domain
-The Email field is required.;The Email field is required.
-The Password field is required.;The Password field is required.
-The ConfirmPassword field is required.;The ConfirmPassword field is required.
-Passwords need to match;Passwords need to match
-Cleanup exception;Cleanup exception
-No shared domain found;No shared domain found
-Searching for deploy plesk server;Searching for deploy plesk server
-No plesk server found;No plesk server found
-No plesk server found to deploy to;No plesk server found to deploy to
-No node found to deploy to;No node found to deploy to
-Website details;Website details
-Configure your website;Configure your website
-The name cannot be longer that 32 characters;The name cannot be longer that 32 characters
-The name should only consist of lower case characters;The name should only consist of lower case characters
-News;News
-Title...;Title...
-Enter text...;Enter text...
-Saving...;Saving...
-Deleting...;Deleting...
-Delete post;Delete post
-Do you really want to delete the post ";Do you really want to delete the post "
-You have no domains;You don't have domains
-We were not able to find any domains associated with your account;We were not able to find any domains associated with your account
-You have no websites;You have no websites
-We were not able to find any websites associated with your account;We were not able to find any websites associated with your account
-Guest;Guest
-You need a domain;You need a domain
-New post;New post
-New entry;New entry

+ 0 - 778
Moonlight/defaultstorage/resources/lang/fr_fr.lang

@@ -1,778 +0,0 @@
-Open support;Assistance ouverte
-About us;À propos de nous
-Imprint;Imprimer
-Privacy;Confidentialité
-Login;Se connecter
-Register;S'enregistrer
-Insert brand name...;Insérer le nom de la marque...
-Save and continue;Sauvegarder et continuer
-Saving;Sauvegarde
-Configure basics;Configurer les bases
-Brand name;Nom de l'entreprise
-test;test
-Insert first name...;Insérer le prénom...
-Insert last name...;Insérer le nom de famille...
-Insert email address...;Insérer l'adresse e-mail...
-Add;Ajouter
-Adding...;Ajout...
-Add admin accounts;Ajouter des comptes administrateur
-First name;Prénom
-Last name;Nom de famille
-Email address;Adresse e-mail
-Enter password;Entrer le mot de passe
-Next;Suivant
-Back;Retour
-Configure features;Configurer les fonctionnalités
-Support chat;Chat d'assistance
-Finish;Finition
-Finalize installation;Finaliser l'installation
-Moonlight basic settings successfully configured;Paramètres de base Moonlight configurés avec succès
-Ooops. This page is crashed;Oups. Cette page est plantée.
-This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page;Cette page est plantée. L'erreur a été signalée à l'équipe Moonlight. En attendant, vous pouvez essayer de recharger la page
-Setup complete;Configuration terminée
-It looks like this moonlight instance is ready to go;Cette instance Moonlight est prête
-User successfully created;Utilisateur créé avec succès
-Ooops. Your moonlight client is crashed;Oups. Votre client moonlight est planté
-This error has been reported to the moonlight team;Cette erreur a été signalée à l'équipe Moonlight
-Sign In;Se connecter
-Sign in to start with moonlight;Connectez-vous pour commencer avec Moonlight
-Sign in with Discord;Connectez-vous avec Discord
-Or with email;Ou avec e-mail
-Forgot password?;Mot de passe oublié?
-Sign-in;Se connecter
-Not registered yet?;Pas encore inscrit?
-Sign up;S'inscrire
-Authenticating;Authentification...
-Sign in with Google;Connectez-vous avec Google
-Working;En fonctionnement...
-Error;Erreur
-Email and password combination not found;Combinaison e-mail et mot de passe introuvable
-Email;E-mail
-Password;Mot de passe
-Account settings;Paramètres du compte
-Logout;Se déconnecter
-Dashboard;Tableau de bord
-Order;Commande
-Website;Site internet
-Database;Base de données
-Domain;Domaine
-Servers;Serveurs
-Websites;Sites Internet
-Databases;Bases de données
-Domains;Domaines
-Changelog;Journal des modifications
-Firstname;Prénom
-Lastname;Nom de famille
-Repeat password;Répéter le mot de passe
-Sign Up;S'inscrire
-Sign up to start with moonlight;Inscrivez-vous pour commencer avec moonlight
-Sign up with Discord;Inscrivez-vous avec Discord
-Sign up with Google;S'inscrire avec Google
-Sign-up;S'inscrire
-Already registered?;Déjà enregistré?
-Sign in;Se connecter
-Create something new;Créer quelque chose de nouveau
-Create a gameserver;Créer un serveur de jeu
-A new gameserver in just a few minutes;Un nouveau serveur de jeu en quelques minutes
-Create a database;Créer une base de données
-A quick way to store your data and manage it from all around the world;Un moyen rapide de stocker vos données et de les gérer partout dans le monde
-Manage your services;Gérez vos prestations
-Manage your gameservers;Gérez vos serveurs de jeux
-Adjust your gameservers;Ajustez vos serveurs de jeu
-Manage your databases;Gérez vos bases de données
-Insert, delete and update the data in your databases;Insérer, supprimer et mettre à jour les données dans vos bases de données
-Create a website;Créer un site internet
-Make your own websites with a webspace;Créez vos propres sites internet avec un espace Web
-Create a domain;Créer un domaine
-Make your servvices accessible throught your own domain;Rendez vos services accessibles via votre propre domaine
-Manage your websites;Gérer vos sites internet
-Modify the content of your websites;Modifier le contenu de vos sites internet
-Manage your domains;Gérez vos domaines
-Add, edit and delete dns records;Ajouter, modifier et supprimer des enregistrements DNS
-Admin;Admin
-System;Système
-Overview;Aperçu
-Manager;Manager
-Cleanup;Nettoyer
-Nodes;Nœuds
-Images;Images
-aaPanel;aaPanel
-Users;Utilisateurs
-Support;Assistance
-Statistics;Statistiques
-No nodes found. Start with adding a new node;Aucun nœud trouvé. Commencez par ajouter un nouveau nœud
-Nodename;Nom du nœud
-FQDN;Nom de domaine complet
-Create;Créer
-Creating;Création...
-Http port;Port HTTP
-Sftp port;Port Sftp
-Moonlight daemon port;Port du daemon Moonlight
-SSL;SSL
-CPU Usage;L'utilisation du processeur
-In %;En %
-Memory;Mémoire
-Used / Available memory;Mémoire utilisée / disponible
-Storage;Stockage
-Available storage;Stockage disponible
-Add a new node;Ajouter un nouveau nœud
-Delete;Supprimer
-Deleting;Suppression...
-Edit;Modifier
-Token Id;Token Id
-Token;Token
-Save;Sauvegarder
-Setup;Configuration
-Open a ssh connection to your node and enter;Ouvrez une connexion ssh à votre nœud et entrez
-and paste the config below. Then press STRG+O and STRG+X to save;et collez la configuration ci-dessous. Appuyez ensuite sur CTRL+O et CTRL+X pour enregistrer
-Before configuring this node, install the daemon;Avant de configurer ce nœud, installez le deamon
-Delete this node?;Supprimer ce nœud ?
-Do you really want to delete this node;Voulez-vous vraiment supprimer ce nœud?
-Yes;Oui
-No;Non
-Status;Statut
-Adding;Ajout
-Port;Port
-Id;Id
-Manage;Gérer
-Create new server;Créer un nouveau serveur
-No servers found;Aucun serveur trouvé
-Server name;Nom du serveur
-Cpu cores;Cœurs de processeur
-Disk;Stockage
-Image;Image
-Override startup;Ignorer le démarrage
-Docker image;Image Docker
-CPU Cores (100% = 1 Core);Cœurs de processeur (100% = 1 cœur)
-Server successfully created;Serveur créé avec succès
-Name;Nom
-Cores;Noyaux
-Owner;Propriétaire
-Value;Valeur
-An unknown error occured;Une erreur inconnue s'est produite
-No allocation found;Aucune attribution trouvée
-Identifier;Identifiant
-UuidIdentifier;Identifiant Uuid
-Override startup command;Remplacer la commande de démarrage
-Loading;Chargement...
-Offline;Hors ligne
-Connecting;Connexion...
-Start;Démarrer
-Restart;Redémarrer
-Stop;Arrêt
-Shared IP;IP partagée
-Server ID;Identifiant du serveur
-Cpu;CPU
-Console;Console
-Files;Fichiers
-Backups;Sauvegardes
-Network;Réseau
-Plugins;Plugins
-Settings;Paramètres
-Enter command;Entrez la commande
-Execute;Exécuter
-Checking disk space;Vérification de l'espace disque
-Updating config files;Mise à jour des fichiers de configuration
-Checking file permissions;Vérification des autorisations de fichiers
-Downloading server image;Téléchargement de l'image du serveur
-Downloaded server image;Image de serveur téléchargée
-Starting;Démarrage
-Online;En ligne
-Kill;Kill
-Stopping;Arrêt
-Search files and folders;Rechercher des fichiers et des dossiers
-Launch WinSCP;Lancer WinSCP
-New folder;Nouveau dossier
-Upload;Upload
-File name;Nom de fichier
-File size;Taille du fichier
-Last modified;Dernière modification
-Cancel;Annuler
-Canceling;Annulation
-Running;En cours d'exécution
-Loading backups;Chargement des sauvegardes
-Started backup creation;Création de sauvegarde démarrée
-Backup is going to be created;La sauvegarde va être crée
-Rename;Renommer
-Move;Déplacer
-Archive;Archiver
-Unarchive;Désarchiver
-Download;Télécharger
-Starting download;Démarrage du téléchargement
-Backup successfully created;Sauvegarde crée avec succès
-Restore;Restaurer
-Copy url;Copier le lien
-Backup deletion started;La suppression de la sauvegarde a commencé
-Backup successfully deleted;Sauvegarde supprimée avec succès
-Primary;Primaire
-This feature is currently not available;Cette fonctionnalité n'est pas disponible actuellement
-Send;Envoyer
-Sending;Envoi en cours
-Welcome to the support chat. Ask your question here and we will help you;Bienvenue sur le chat d'assistance. Posez votre question ici et nous vous aiderons
- minutes ago; minutes
-just now;à l'instant
-less than a minute ago;il y a moins d'une minute
-1 hour ago;Il ya 1 heure
-1 minute ago;il y a 1 minute
-Failed;Échec
- hours ago; Heures
-Open tickets;Tickets ouverts
-Actions;Actions
-No support ticket is currently open;Aucun ticket d'assistance n'est actuellement ouvert
-User information;Informations de l'utilisateur
-Close ticket;Fermer le ticket
-Closing;Fermeture...
-The support team has been notified. Please be patient;L'équipe d'assistance a été prévenue. S'il vous plaît soyez patient
-The ticket is now closed. Type a message to open it again;Le ticket est maintenant fermé. Tapez un message pour l'ouvrir à nouveau
-1 day ago;il y a 1 jour
-is typing;est en train d'écrire...
-are typing;sont en train de taper
-No domains available;Aucun domaine disponible
-Shared domains;Domaines partagés
-Shared domain;Domaine partagé
-Shared domain successfully deleted;Domaine partagé supprimé avec succès
-Shared domain successfully added;Domaine partagé ajouté avec succès
-Domain name;Nom de domaine
-DNS records for;enregistrements DNS pour
-Fetching dns records;Récupération des enregistrements DNS
-No dns records found;Aucun enregistrement DNS trouvé
-Content;Contenu
-Priority;Priorité
-Ttl;TTL
-Enable cloudflare proxy;Activer le proxy cloudflare
-CF Proxy;Proxy CF
- days ago; Jours
-Cancle;Annuler
-An unexpected error occured;Une erreur inattendue s'est produite
-Testy;Testy
-Error from cloudflare api;Erreur de l'API cloudflare
-Profile;Profil
-No subscription available;Aucun abonnement disponible
-Buy;Acheter
-Redirecting;Redirection
-Apply;Appliquer
-Applying code;Appliquer le code
-Invalid subscription code;Code d'abonnement invalide
-Cancel Subscription;Annuler l'abonnement
-Active until;Actif jusqu'à
-We will send you a notification upon subscription expiration;Nous vous enverrons une notification à l'expiration de l'abonnement
-This token has been already used;Ce jeton a déjà été utilisé
-New login for;Nouvelle connexion pour
-No records found for this day;Aucun enregistrement trouvé pour ce jour
-Change;Changer
-Changing;En changement
-Minecraft version;Version Minecraft
-Build version;Version
-Server installation is currently running;L'installation du serveur est en cours
-Selected;Choisi
-Move deleted;Déplacement supprimé
-Delete selected;Supprimer sélectionnée
-Log level;Niveau de journalisation
-Log message;Message de journal
-Time;Temps
-Version;Version
-You are running moonlight version;Vous utilisez la version Moonlight
-Operating system;Système opérateur
-Moonlight is running on;Moonlight est en cours d'exécution
-Memory usage;Utilisation de la mémoire
-Moonlight is using;Moonlight est utilisé
-of memory;de mémoire
-Cpu usage;L'utilisation du processeur
-Refresh;Rafraîchir
-Send a message to all users;Envoyer un message à tous les utilisateurs
-IP;IP
-URL;URL
-Device;Appareil
-Change url;Modifier l'URL
-Message;Message
-Enter message;Entrez le message
-Enter the message to send;Entrez le message à envoyer
-Confirm;Confirmer
-Are you sure?;Etes-vous sûr?
-Enter url;Entrer l'URL
-An unknown error occured while starting backup deletion;Une erreur inconnue s'est produite lors du démarrage de la suppression de la sauvegarde
-Success;Succès
-Backup URL successfully copied to your clipboard;URL de sauvegarde copiée avec succès dans votre presse-papiers
-Backup restore started;La restauration de la sauvegarde a commencé
-Backup successfully restored;Sauvegarde restaurée avec succès
-Register for;S'inscrire à
-Core;Cœur
-Logs;Logs
-AuditLog;AuditLog
-SecurityLog;SecurityLog
-ErrorLog;ErrorLog
-Resources;Ressources
-WinSCP cannot be launched here;WinSCP ne peut pas être lancé ici
-Create a new folder;Créer un nouveau dossier
-Enter a name;Entrez un nom
-File upload complete;Téléchargement du fichier terminé
-New server;Nouveau serveur
-Sessions;Session
-New user;Nouvel utilisateur
-Created at;Créé à
-Mail template not found;Modèle de courrier introuvable
-Missing admin permissions. This attempt has been logged ;Autorisations d'administrateur manquantes. Cette tentative a été enregistrée
-Address;Adresse
-City;Ville
-State;État
-Country;Pays
-Totp;Totp
-Discord;Discord
-Subscription;Abonnement
-None;Aucun
-No user with this id found;Aucun utilisateur avec cet identifiant n'a été trouvé
-Back to list;Retour à la liste
-New domain;Nouveau domaine
-Reset password;Réinitialiser le mot de passe
-Password reset;Réinitialisation du mot de passe
-Reset the password of your account;Réinitialiser le mot de passe de votre compte
-Wrong here?;Faux ici?
-A user with this email can not be found;Un utilisateur avec cet e-mail est introuvable
-Password reset successfull. Check your mail;Réinitialisation du mot de passe réussie. Vérifier votre e-mail
-Discord bot;Discord Bot
-New image;Nouvelle image
-Description;Description
-Uuid;UUID
-Enter tag name;Entrez le nom de la balise
-Remove;Retirer
-No tags found;Aucun tag trouvé
-Enter docker image name;Entrez le nom de l'image Docker
-Tags;Mots clés
-Docker images;Images Docker
-Default image;Image par défaut
-Startup command;Commande de démarrage
-Install container;Installer le conteneur
-Install entry;Installer l'entrée
-Configuration files;Fichiers de configuration
-Startup detection;Détection de démarrage
-Stop command;Commande d'arrêt
-Successfully saved image;Image enregistrée avec succès
-No docker images found;Aucune image docker trouvée
-Key;Clé
-Default value;Valeur par défaut
-Allocations;Allocations
-No variables found;Aucune variable trouvée
-Successfully added image;Image ajoutée avec succès
-Password change for;Changement de mot de passe pour
-of;de
-New node;Nouveau nœud
-Fqdn;Fqdn
-Cores used;Cœurs utilisés
-used;utilisé
-5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64
-Host system information;Informations sur le système hôte
-0;0
-Docker containers running;Conteneurs Docker en cours d'exécution
-details;Détails
-1;1
-2;2
-DDos;DDos
-No ddos attacks found;Aucune attaque ddos trouvée
-Node;Nœud
-Date;Date
-DDos attack started;L'attaque DDos a commencé
-packets;paquets
-DDos attack stopped;Attaque DDos stoppée
- packets; paquets
-Stop all;Arrête tout
-Kill all;Tuer tous
-Network in;Réseau dans
-Network out;Sortie réseau
-Kill all servers;Tuez tous les serveurs
-Do you really want to kill all running servers?;Voulez-vous vraiment tuer tous les serveurs en cours d'exécution?
-Change power state for;Changer l'état de l'alimentation pour
-to;à
-Stop all servers;Arrêtez tous les serveurs
-Do you really want to stop all running servers?;Voulez-vous vraiment arrêter tous les serveurs en cours d'exécution?
-Manage ;Gérer 
-Manage user ;Gérer l'utilisateur 
-Reloading;Rechargement...
-Update;Mise à jour
-Updating;Mise à jour...
-Successfully updated user;Utilisateur mis à jour avec succès
-Discord id;Discord ID
-Discord username;Nom d'utilisateur Discord
-Discord discriminator;Discord Tag
-The Name field is required.;Il est requis de compléter le champ correspondant au nom.
-An error occured while logging you in;Une erreur s'est produite lors de votre connexion
-You need to enter an email address;Vous devez saisir une adresse e-mail
-You need to enter a password;Vous devez entrer un mot de passe
-You need to enter a password with minimum 8 characters in lenght;Vous devez saisir un mot de passe d'au moins 8 caractères
-Proccessing;Traitement...
-The FirstName field is required.;Le champ Prénom est obligatoire.
-The LastName field is required.;Le champ Nom est obligatoire.
-The Address field is required.;Le champ Adresse est obligatoire.
-The City field is required.;Le champ Ville est obligatoire.
-The State field is required.;Le champ État est obligatoire.
-The Country field is required.;Le champ Pays est obligatoire.
-Street and house number requered;Rue et numéro de maison requis
-Max lenght reached;Longueur max atteinte
-Server;Serveur
-stopped;arrêté
-Cleanups;Nettoyages
-executed;réalisé
-Used clanup;nettoyage utilisé
-Enable;Activer
-Disable;Désactiver
-Addons;Compléments
-Javascript version;Version Javascript
-Javascript file;Fichier Javascript
-Select javascript file to execute on start;Sélectionnez le fichier javascript à exécuter au démarrage
-Submit;Soumettre
-Processing;Traitement...
-Go up;monter
-Running cleanup;Nettoyage en cours
-servers;Serveurs
-Select folder to move the file(s) to;Sélectionnez le dossier dans lequel déplacer le(s) fichier(s)
-Paper version;Version paper
-Join2Start;Join2Start
-Server reset;Réinitialisation du serveur
-Reset;Réinitialiser
-Resetting;Réinitialisation...
-Are you sure you want to reset this server?;Voulez-vous vraiment réinitialiser ce serveur?
-Are you sure? This cannot be undone;Etes-vous sûr? cela ne peut être annulé
-Resetting server;Réinitialisation du serveur...
-Deleted file;Fichier supprimé
-Reinstalling server;Réinstallation du serveur
-Uploading files;Téléchargement des fichiers
-complete;complet
-Upload complete;Téléchargement complet
-Security;Sécurité
-Subscriptions;Abonnements
-2fa Code;Code 2FA
-Your account is secured with 2fa;Votre compte est sécurisé avec 2fa
-anyone write a fancy text here?;quelqu'un écrit un texte de fantaisie ici?
-Activate 2fa;Activer 2FA
-2fa apps;applications 2FA
-Use an app like ;Utilisez une application comme 
-or;ou
-and scan the following QR Code;et scannez le QR Code suivant
-If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:;Si vous rencontrez des difficultés pour utiliser le QR Code, sélectionnez la saisie manuelle dans l'application et entrez votre email et le code suivant:
-Finish activation;Terminer l'activation
-2fa Code requiered;Code 2FA requis
-New password;Nouveau mot de passe
-Secure your account;Sécurisez votre compte
-2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.;2FA ajoute une autre couche de sécurité à votre compte. Vous devez entrer un code à 6 chiffres pour vous connecter.
-New subscription;Nouvel abonnement
-You need to enter a name;Vous devez entrer un nom
-You need to enter a description;Vous devez entrer une description
-Add new limit;Ajouter une nouvelle limite
-Create subscription;Créer un abonnement
-Options;Options
-Amount;Montant
-Do you really want to delete it?;Voulez-vous vraiment le supprimer?
-Loading your subscription;Chargement de votre abonnement
-Searching for deploy node;Recherche d'un nœud de déploiement
-Searching for available images;Recherche d'images disponibles
-Server details;Détails du serveur
-Configure your server;Configurez votre serveur
-Default;Défaut
-You reached the maximum amount of servers for every image of your subscription;Vous avez atteint le nombre maximum de serveurs pour chaque image de votre abonnement.
-Personal information;Informations personnelles
-Enter code;Entrez le code
-Server rename;Renommer le serveur
-Create code;créer un code
-Save subscription;Enregistrer l'abonnement
-Enter your information;Entrez vos informations
-You need to enter your full name in order to use moonlight;Vous devez entrer votre nom complet pour utiliser moonlight
-No node found;Aucun nœud trouvé
-No node found to deploy to found;Aucun noeud trouvé à monter
-Node offline;Nœud hors ligne
-The node the server is running on is currently offline;Le nœud sur lequel le serveur s'exécute est actuellement hors ligne
-Server not found;serveur introuvable
-A server with that id cannot be found or you have no access for this server;Un serveur avec cet identifiant est introuvable ou vous n'avez pas accès à ce serveur
-Compress;Compresser
-Decompress;Décompresser
-Moving;Déplacement...
-Compressing;Compression...
-selected;choisi
-New website;Nouveau site internet
-Plesk servers;Serveurs Plesk
-Base domain;Domaine de base
-Plesk server;Serveur Plesk
-Ftp;FTP
-No SSL certificate found;Aucun certificat SSL trouvé
-Ftp Host;Hôte FTP
-Ftp Port;Port FTP
-Ftp Username;Nom d'utilisateur FTP
-Ftp Password;Mot de passe FTP
-Use;Utiliser
-SSL Certificates;Certificats SSL
-SSL certificates;Certificats SSL
-Issue certificate;Délivrer un certificat
-New plesk server;Nouveau serveur plesk
-Api url;URL de l'API
-Host system offline;Système hôte hors ligne
-The host system the website is running on is currently offline;Le système hôte sur lequel le site internet s'exécute est actuellement hors ligne
-No SSL certificates found;Aucun certificat SSL trouvé
-No databases found for this website;Aucune base de données trouvée pour ce site internet
-The name should be at least 8 characters long;Le nom doit comporter au moins 8 caractères
-The name should only contain of lower case characters and numbers;Le nom ne doit contenir que des caractères minuscules et des chiffres
-Error from plesk;Erreur de plesk
-Host;Hôte
-Username;Nom d'utilisateur
-SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one;Les enregistrements SRV ne peuvent pas être mis à jour grâce au client api cloudflare. Veuillez supprimer l'enregistrement et en créer un nouveau
-The User field is required.;Le champ Utilisateur est obligatoire.
-You need to specify a owner;Vous devez spécifier un propriétaire
-You need to specify a image;Vous devez spécifier une image
-Api Url;URL de l'API
-Api Key;Clé API
-Duration;Durée
-Enter duration of subscription;Saisir la durée de l'abonnement
-Copied code to clipboard;Code copié dans le presse-papiers
-Invalid or expired subscription code;Code d'abonnement invalide ou expiré
-Current subscription;Abonnement actuel
-You need to specify a server image;Vous devez spécifier une image de serveur
-CPU;CPU
-Hour;Heure
-Day;Jour
-Month;Mois
-Year;Année
-All time;Tout le temps
-This function is not implemented;Cette fonction n'est pas implémentée
-Domain details;Détails du domaine
-Configure your domain;Configurez votre domaine
-You reached the maximum amount of domains in your subscription;Vous avez atteint le nombre maximum de domaines dans votre abonnement
-You need to specify a shared domain;Vous devez spécifier un domaine partagé
-A domain with this name does already exist for this shared domain;Un domaine portant ce nom existe déjà pour ce domaine partagé
-The Email field is required.;Le champ E-mail est obligatoire.
-The Password field is required.;Le champ Mot de passe est obligatoire.
-The ConfirmPassword field is required.;Le champ Confirmer le mot de passe est obligatoire.
-Passwords need to match;Les mots de passe doivent correspondre
-Cleanup exception;Exception de nettoyage
-No shared domain found;Aucun domaine partagé trouvé
-Searching for deploy plesk server;Recherche de déploiement du serveur Plesk
-No plesk server found;Aucun serveur plesk trouvé
-No plesk server found to deploy to;Aucun serveur plesk trouvé pour le déploiement
-No node found to deploy to;Aucun nœud trouvé pour le déploiement
-Website details;Détails du site internet
-Configure your website;Configurez votre site internet
-The name cannot be longer that 32 characters;Le nom ne peut pas dépasser 32 caractères
-The name should only consist of lower case characters;Le nom ne doit être composé que de caractères minuscules
-News;Nouvelles
-Title...;Titre...
-Enter text...;Entrez du texte...
-Saving...;Sauvegarde...
-Deleting...;Suppression...
-Delete post;Supprimer le message
-Do you really want to delete the post ";Voulez-vous vraiment supprimer le message "
-You have no domains;Vous n'avez aucun domaine
-We were not able to find any domains associated with your account;Nous n'avons trouvé aucun domaine associé à votre compte
-You have no websites;Vous n'avez pas de sites internet
-We were not able to find any websites associated with your account;Nous n'avons trouvé aucun site internet associé à votre compte
-Guest;Invité
-You need a domain;Vous avez besoin d'un domaine
-New post;Nouveau poste
-New entry;Nouvelle entrée
-You have no servers;Vous n'avez pas de serveurs
-We were not able to find any servers associated with your account;Nous n'avons trouvé aucun serveur associé à votre compte
-Error creating server on wings;Erreur lors de la création du serveur sur les ailes
-An unknown error occured while restoring a backup;Une erreur inconnue s'est produite lors de la restauration d'une sauvegarde
-Error from daemon;Erreur du daemon
-End;Fin
-Cloud panel;Cloud panel
-Cloud panels;Cloud panels
-New cloud panel;Nouveau Panel cloud 
-You need to enter an api key;Vous devez entrer une clé API
-Webspaces;Espaces webs
-New webspace;Nouvel espace web
-The uploaded file should not be bigger than 100MB;Le fichier téléchargé ne doit pas dépasser 100 Mo
-An unknown error occured while uploading a file;Une erreur inconnue s'est produite lors du téléchargement d'un fichier
-No databases found for this webspace;Aucune base de données trouvée pour cet espace Web
-Sftp;SFTP
-Sftp Host;Hôte Sftp
-Sftp Port;Port Sftp
-Sftp Username;Nom d'utilisateur Sftp
-Sftp Password;Mot de passe Sftp
-Lets Encrypt certificate successfully issued;Certificat Lets Encrypt émis avec succès
-Add shared domain;Ajouter un domaine partagé
-Webspace;Espace web
-You reached the maximum amount of websites in your subscription;Vous avez atteint le nombre maximum de sites internet dans votre abonnement
-Searching for deploy web host;Recherche d'hébergeur web déployé
-Webspace details;Détails de l'espace Web
-Web host;Hébergeur
-Configure your webspaces;Configurez vos espaces web
-You reached the maximum amount of webspaces in your subscription;Vous avez atteint le nombre maximum d'espaces Web dans votre abonnement
-Create a webspace;Créer un espace Web
-Manage your webspaces;Gérez vos espaces web
-Modify the content of your webspaces;Modifier le contenu de vos espaces web
-Successfully updated password;Mot de passe mis à jour avec succès
-An unknown error occured while sending your message;Une erreur inconnue s'est produite lors de l'envoi de votre message
-Open chats;Ouvrir les messages
-No message sent yet;Aucun message envoyé pour le moment
-Support ticket open;Ticket d'assistance ouvert
-Support ticket closed;Ticket d'assistance fermé
-Your connection has been paused;Votre connexion a été interrompue
-We paused your connection because of inactivity. The resume just focus the tab and wait a few seconds;Nous avons suspendu votre connexion pour cause d'inactivité. Passez à l'onglet et attendez quelques secondes
-Failed to reconnect to the moonlight servers;Échec de la reconnexion aux serveurs Moonlight
-We were unable to reconnect to moonlight. Please refresh the page;Nous n'avons pas pu nous reconnecter à moonlight. Veuillez actualiser la page
-Failed to reconnect to the moonlight servers. The connection has been rejected;Échec de la reconnexion aux serveurs Moonlight. La connexion a été rejetée
-We were unable to reconnect to moonlight. Most of the time this is caused by an update of moonlight. Please refresh the page;Nous n'avons pas pu nous reconnecter à moonlight. La plupart du temps, cela est causé par une mise à jour du clair de lune. Veuillez actualiser la page
-Verifying token, loading user data;Vérification du jeton, chargement des données utilisateur
-Reload config;Recharger la configuration
-Successfully reloading configuration;Rechargement de la configuration réussi...
-Successfully reloaded configuration;Configuration rechargée avec succès
-Flows;Flux
-Add node;Ajouter un nœud
-Web system;Système Internet
-Servers with this image;Serveurs avec cette image
-You need to specify a user;Vous devez spécifier un utilisateur
-Import;Importer
-Export;Exporter
-Exporting;Exportation
-Successfully imported image;Image importée avec succès
-Forge version;Forge Version
-Fabric version;Fabric Version
-Fabric loader version;Fabric Loader Version
-Rate;Taux
-Hey, can i borrow you for a second?;Hey, je peux vous empruntez une seconde ?
-We want to improve our services and get a little bit of feedback how we are currently doing. Please leave us a rating;Nous voulons améliorer nos services et obtenir un peu de rétroaction sur la façon dont nous faisons actuellement. Merci de nous laisser une note:
-Thanks for your rating;Merci pour votre évaluation
-It would be really kind of you rating us on a external platform as it will help our project very much;Ce serait vraiment gentil de votre part de nous noter sur une plateforme externe car cela aidera beaucoup notre projet
-Close;Fermer
-Rating saved;Note enregistrée
-Group;Groupe
-Beta;Bêta
-Create a new group;Créer un nouveau groupe
-Download WinSCP;Télécharger WinSCP
-Show connection details;Afficher les détails de la connexion
-New;Nouveau
-New file;Nouveau fichier
-Connection details;Détails de connexion
-Malware;Logiciels malveillants
-Create a new file;Créer un nouveau fichier
-Edit layout;Modifier la mise en page
-Uptime;Disponibilité
-Moonlight is online since;Moonlight est en ligne depuis
-User;Utilisateur
-Databases;Bases de données
-Sitzungen;Sitzungen
-Active users;Utilisateurs actifs
-Search for plugins;Rechercher des plugins
-Search;Recherche
-Searching;Recherche...
-Successfully installed gunshell;Cartouche installée avec succès
-Successfully installed fastasyncworldedit;Fastasyncworldedit a été installé avec succès
-Successfully installed minimotd;Minimotd installé avec succès
-Moonlight health;Statut Moonlight
-Healthy;En bonne santé
-Successfully saved file;Fichier enregistré avec succès
-Unsorted servers;Serveurs non triés
-Enter a new name;Entrez un nouveau nom
-Sign in with;Se connecter avec
-Make your services accessible through your own domain;Rendez vos services accessibles via votre propre domaine
-New group;Nouveau groupe
-Finish editing layout;Terminer la modification de la mise en page
-Remove group;Supprimer le groupe
-Hidden in edit mode;Masqué en mode édition
-Enter your 2fa code here;Entrez votre code 2FA ici
-Two factor authentication;Authentification à deux facteurs
-Preferences;Préférences
-Streamer mode;Mode diffusion
-Scan the QR code and enter the code generated by the app you have scanned it in;Scannez le code QR et entrez le code généré par l'application dans laquelle vous l'avez scanné
-Start scan;Lancer l'analyse
-Results;Résultats
-Scan in progress;Analyse en cours
-Debug;Debug
-Save changes;Sauvegarder les modifications
-Delete domain;Supprimer le domaine
-Python version;Version Python 
-Python file;Fichier Python
-Select python file to execute on start;Sélectionnez le fichier python à exécuter au démarrage
-You have no webspaces;Vous n'avez pas d'espaces Web
-We were not able to find any webspaces associated with your account;Nous n'avons trouvé aucun espace Web associé à votre compte
-Backup download successfully started;Le téléchargement de la sauvegarde a démarré avec succès
-Error from cloud panel;Erreur du Cloud Panel
-Error from wings;Erreur des ailes
-Remove link;Supprimer le lien
-Your account is linked to a discord account;Votre compte est lié à un compte discord
-You are able to use features like the discord bot of moonlight;Vous pouvez utiliser des fonctionnalités telles que le bot discord de Moonlight
-The password should be at least 8 characters long;Le mot de passe doit comporter au moins 8 caractères
-The name should only consist of lower case characters or numbers;Le nom ne doit être composé que de caractères minuscules ou de chiffres
-The requested resource was not found;La ressource demandée n'a pas été trouvée
-We were not able to find the requested resource. This can have following reasons;Nous n'avons pas pu trouver la ressource demandée. Cela peut avoir les raisons suivantes
-The resource was deleted;La ressource a été supprimée
-You have to permission to access this resource;Vous devez être autorisé à accéder à cette ressource
-You may have entered invalid data;Vous avez peut-être entré des données invalides
-A unknown bug occured;Un bug inconnu s'est produit
-An api was down and not proper handled;Une API était en panne et n'était pas correctement gérée
-A database with this name does already exist;Une base de données portant ce nom existe déjà
-Successfully installed quickshop-hikari;Quickshop-hikari a été installé avec succès
-You need to enter a valid domain;Vous devez entrer un domaine valide
-2fa code;code 2FA
-This feature is not available for;Cette fonction n'est pas disponible pour
-Your account is currently not linked to discord;Votre compte n'est actuellement pas lié à discord
-To use features like the discord bot, link your moonlight account with your discord account;Pour utiliser des fonctionnalités telles que le bot Discord, liez votre compte Moonlight à votre compte Discord
-Link account;Lien de compte
-Continue;Continuer
-Preparing;Préparation
-Make sure you have installed one of the following apps on your smartphone and press continue;Assurez-vous d'avoir installé l'une des applications suivantes sur votre smartphone et appuyez sur continuer
-The max lenght for the name is 32 characters;La longueur maximale du nom est de 32 caractères
-Successfully installed chunky;Chunky installé avec succès
-Successfully installed huskhomes;Hulkhomes installés avec succès
-Successfully installed simply-farming;Simple-farming installé avec succès
-You need to specify a first name;Vous devez indiquer un prénom
-You need to specify a last name;Vous devez spécifier un nom de famille
-You need to specify a password;Vous devez spécifier un mot de passe
-Please solve the captcha;Merci de résoudre le captcha
-The email is already in use;L'e-mail est déjà utilisé
-The dns records of your webspace do not point to the host system;Les enregistrements DNS de votre espace Web ne pointent pas vers le système hôte
-Scan complete;Analyse terminée
-Currently scanning:;Analyse en cours:
-Successfully installed dynmap;Dynamap installé avec succès
-Successfully installed squaremap;Squaremap installé avec succès
-No web host found;Aucun hébergeur trouvé
-No web host found to deploy to;Aucun hébergeur trouvé pour le déploiement
-Successfully installed sleeper;Sleeper installé avec succès
-You need to enter a domain;Vous devez saisir un domaine
-Enter a ip;Entrez une ip
-Ip Bans;Ip Bans
-Ip;Ip
-Successfully installed simple-voice-chat;Simple-voice-chat installé avec succès
-Successfully installed smithing-table-fix;Smithing-table-fix a été installé avec succès
-Successfully installed justplayer-tpa;Justplayer-TPA installé avec succès
-Successfully installed ishop;iShop installé avec succès
-Successfully installed lifestealre;Lifestealre installé avec succès
-Successfully installed lifeswap;Lifeswap installé avec succès
-Java version;Java Version
-Jar file;Fichier Jar
-Select jar to execute on start;Sélectionnez le jar à exécuter au démarrage
-A website with this domain does already exist;Un site Web avec ce domaine existe déjà
-Successfully installed discordsrv;Discordsrv a été installé avec succès
-Reinstall;Réinstaller
-Reinstalling;Réinstallation
-Successfully installed freedomchat;Freedomchat installé avec succès
-Leave empty for the default background image;Laisser vide pour l'image d'arrière-plan par défaut
-Background image url;URL de l'image d'arrière-plan
-of CPU used;du processeur utilisé
-memory used;mémoire utilisée
-163;163
-172;172
-Sentry;Sentinelle
-Sentry is enabled;La sentinelle est activée
-Successfully installed mobis-homes;Mobis-homes installés avec succès
-Your moonlight account is disabled;Votre compte moonlight est désactivé
-Your moonlight account is currently disabled. But dont worry your data is still saved;Votre compte Moonlight est actuellement désactivé. Mais ne vous inquiétez pas, vos données sont toujours enregistrées
-You need to specify a email address;Vous devez indiquer une adresse e-mail
-A user with that email does already exist;Un utilisateur avec cet email existe déjà
-Successfully installed buildmode;buildmode installé avec succès
-Successfully installed plasmo-voice;Plasmo-Voice installé avec succès
-157;157
-174;174
-158;158
-Webspace not found;Espace Web introuvable
-A webspace with that id cannot be found or you have no access for this webspace;Un espace Web avec cet identifiant est introuvable ou vous n'avez pas accès à cet espace Web
-No plugin download for your minecraft version found;Aucun téléchargement de plugin pour votre version de minecraft trouvé
-Successfully installed gamemode-alias;Gamemode-alias installé avec succès
-228;228
-User;Utilisateur
-Send notification;Envoyer une notification
-Successfully saved changes;Modifications enregistrées avec succès
-Archiving;Archivage...
-Server is currently not archived;Le serveur n'est actuellement pas archivé
-Add allocation;Ajouter une allocation
-231;231
-175;175
-Dotnet version;Version Dotnet
-Dll file;Fichier DLL
-Select dll to execute on start;Sélectionnez le dll à exécuter au démarrage

+ 0 - 778
Moonlight/defaultstorage/resources/lang/ro_ro.lang

@@ -1,778 +0,0 @@
-Open support;Suport deschis
-About us;Despre noi
-Imprint;Impresum
-Privacy;Confidențialitate
-Login;Autentificare
-Register;Înregistrare
-Insert brand name...;Introduceți numele mărcii...
-Save and continue;Salvați și continuați
-Saving;Se salvează
-Configure basics;Configurare elemente de bază
-Brand name;Numele mărcii
-test;test
-Insert first name...;Introduceți prenumele...
-Insert last name...;Introduceți numele de familie...
-Insert email address...;Introduceți adresa de email...
-Add;Adăugați
-Adding...;Se adaugă...
-Add admin accounts;Adăugați conturi de administrator
-First name;Prenume
-Last name;Nume de familie
-Email address;Adresă de email
-Enter password;Introduceți parola
-Next;Următorul
-Back;Înapoi
-Configure features;Configurați funcționalitățile
-Support chat;Asistență prin chat
-Finish;Finalizare
-Finalize installation;Finalizați instalarea
-Moonlight basic settings successfully configured;Setările de bază Moonlight au fost configurate cu succes
-Ooops. This page is crashed;Hopa. Această pagină s-a blocat.
-This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page;Această pagină s-a blocat. Eroarea a fost raportată echipei Moonlight. Între timp, puteți încerca să reîncărcați pagina
-Setup complete;Configurare completă
-It looks like this moonlight instance is ready to go;Se pare că această instanță Moonlight este gata de utilizare
-User successfully created;Utilizator creat cu succes
-Ooops. Your moonlight client is crashed;Hopa. Clientul tău Moonlight s-a blocat.
-This error has been reported to the moonlight team;Această eroare a fost raportată echipei Moonlight
-Sign In;Autentificare
-Sign in to start with moonlight;Autentificați-vă pentru a începe cu Moonlight
-Sign in with Discord;Autentificare cu Discord
-Or with email;Sau cu email
-Forgot password?;Ați uitat parola?
-Sign-in;Autentificare
-Not registered yet?;Încă nu sunteți înregistrat?
-Sign up;Înregistrare
-Authenticating;Se autentifică...
-Sign in with Google;Autentificare cu Google
-Working;Se lucrează...
-Error;Eroare
-Email and password combination not found;Combinarea de email și parolă nu a fost găsită
-Email;Email
-Password;Parolă
-Account settings;Setări cont
-Logout;Deconectare
-Dashboard;Panou de control
-Order;Comandă
-Website;Website
-Database;Bază de date
-Domain;Domeniu
-Servers;Servere
-Websites;Site-uri web
-Databases;Baze de date
-Domains;Domenii
-Changelog;Jurnal de modificări
-Firstname;Prenume
-Lastname;Nume de familie
-Repeat password;Repetă parola
-Sign Up;Înregistrare
-Sign up to start with moonlight;Înregistrați-vă pentru a începe cu Moonlight
-Sign up with Discord;Înregistrare cu Discord
-Sign up with Google;Înregistrare cu Google
-Sign-up;Înregistrare
-Already registered?;Deja înregistrat?
-Sign in;Autentificare
-Create something new;Creați ceva nou
-Create a gameserver;Creați un server de jocuri
-A new gameserver in just a few minutes;Un server de jocuri nou în doar câteva minute
-Create a database;Creați o bază de date
-A quick way to store your data and manage it from all around the world;O modalitate rapidă de a stoca datele și de a le gestiona din întreaga lume
-Manage your services;Gestionați serviciile dumneavoastră
-Manage your gameservers;Gestionați serverele dumneavoastră de jocuri
-Adjust your gameservers;Reglați serverele dumneavoastră de jocuri
-Manage your databases;Gestionați bazele de date
-Insert, delete and update the data in your databases;Introduceți, ștergeți și actualizați datele din bazele de date
-Create a website;Creați un site web
-Make your own websites with a webspace;Creați propriile site-uri web cu un spațiu web
-Create a domain;Creați un domeniu
-Make your services accessible through your own domain;Faceți serviciile dumneavoastră accesibile prin propriul domeniu
-Manage your websites;Gestionați site-urile web
-Modify the content of your websites;Modificați conținutul site-urilor dumneavoastră
-Manage your domains;Gestionați domeniile dumneavoastră
-Add, edit, and delete DNS records;Adăugați, editați și ștergeți înregistrări DNS
-Admin;Administrator
-System;Sistem
-Overview;Prezentare generală
-Manager;Manager
-Cleanup;Curățare
-Nodes;Noduri
-Images;Imagini
-aaPanel;aaPanel
-Users;Utilizatori
-Support;Asistență
-Statistics;Statistici
-No nodes found. Start with adding a new node;Nu s-au găsit noduri. Începeți prin adăugarea unui nod nou
-Nodename;Nume nod
-FQDN;Nume complet de domeniu (FQDN)
-Create;Creare
-Creating;Se creează...
-Http port;Port HTTP
-Sftp port;Port SFTP
-Moonlight daemon port;Port daemon Moonlight
-SSL;SSL
-CPU Usage;Utilizare CPU
-In %;În %
-Memory;Memorie
-Used / Available memory;Memorie utilizată / Memorie disponibilă
-Storage;Stocare
-Available storage;Stocare disponibilă
-Add a new node;Adăugați un nod nou
-Delete;Ștergere
-Deleting;Se șterge...
-Edit;Editare
-Token Id;ID token
-Token;Token
-Save;Salvare
-Setup;Configurare
-Open a ssh connection to your node and enter;Deschideți o conexiune SSH la nodul dvs. și introduceți
-and paste the config below. Then press STRG+O and STRG+X to save;și lipiți configurația de mai jos. Apoi apăsați STRG+O și STRG+X pentru a salva
-Before configuring this node, install the daemon;Înainte de a configura acest nod, instalați daemonul
-Delete this node?;Doriți să ștergeți acest nod?
-Do you really want to delete this node;Doriți cu adevărat să ștergeți acest nod?
-Yes;Da
-No;Nu
-Status;Stare
-Adding;Se adaugă
-Port;Port
-Id;ID
-Manage;Administrare
-Create new server;Creați un server nou
-No servers found;Niciun server găsit
-Server name;Nume server
-Cpu cores;Nuclee CPU
-Disk;Disc
-Image;Imagine
-Override startup;Suprascrie pornirea
-Docker image;Imagine Docker
-CPU Cores (100% = 1 Core);Nuclee CPU (100% = 1 nucleu)
-Server successfully created;Server creat cu succes
-Name;Nume
-Cores;Nuclee
-Owner;Proprietar
-Value;Valoare
-An unknown error occurred;A apărut o eroare necunoscută
-No allocation found;Nicio alocare găsită
-Identifier;Identificator
-UuidIdentifier;UuidIdentifier
-Override startup command;Suprascrieți comanda de pornire
-Loading;Se încarcă...
-Offline;Deconectat
-Connecting;Conectare...
-Start;Pornire
-Restart;Repornire
-Stop;Oprire
-Shared IP;IP partajată
-Server ID;ID server
-Cpu;CPU
-Console;Consolă
-Files;Fișiere
-Backups;Copii de siguranță
-Network;Rețea
-Plugins;Plugin-uri
-Settings;Setări
-Enter command;Introduceți comanda
-Execute;Executare
-Checking disk space;Verificare spațiu pe disc
-Updating config files;Actualizare fișiere de configurație
-Checking file permissions;Verificare permisiuni de fișiere
-Downloading server image;Se descarcă imaginea serverului
-Downloaded server image;Imaginea serverului a fost descărcată
-Starting;Începere
-Online;Conectat
-Kill;Oprit forțat
-Stopping;Se oprește
-Search files and folders;Căutare fișiere și foldere
-Launch WinSCP;Lansați WinSCP
-New folder;Folder nou
-Upload;Încărcare
-File name;Nume fișier
-File size;Mărime fișier
-Last modified;Ultima modificare
-Cancel;Anulare
-Canceling;Se anulează
-Running;În execuție
-Loading backups;Se încarcă copiile de siguranță
-Started backup creation;Crearea copiei de siguranță a început
-Backup is going to be created;Se va crea o copie de siguranță
-Rename;Redenumire
-Move;Mutare
-Archive;Arhivare
-Unarchive;Dezarhivare
-Download;Descărcare
-Starting download;Începerea descărcării
-Backup successfully created;Copie de siguranță creată cu succes
-Restore;Restaurare
-Copy url;Copiere URL
-Backup deletion started;Se începe ștergerea copiei de siguranță
-Backup successfully deleted;Copie de siguranță ștearsă cu succes
-Primary;Primar
-This feature is currently not available;Această funcționalitate nu este disponibilă în prezent
-Send;Trimite
-Sending;Se trimite
-Welcome to the support chat. Ask your question here and we will help you;Bine ați venit la chatul de asistență. Puneți-vă întrebarea aici și vă vom ajuta
-minutes ago;minute în urmă
-just now;chiar acum
-less than a minute ago;mai puțin de o minută în urmă
-1 hour ago;acum 1 oră
-1 minute ago;acum 1 minut
-Failed;Eșuat
-hours ago;ore în urmă
-Open tickets;Deschideți tichete
-Actions;Acțiuni
-No support ticket is currently open;Nu există momentan niciun tichet de asistență deschis
-User information;Informații utilizator
-Close ticket;Închideți tichetul
-Closing;Se închide
-The support team has been notified. Please be patient;Echipa de suport a fost notificată. Vă rugăm să aveți răbdare
-The ticket is now closed. Type a message to open it again;Tichetul este acum închis. Tastați un mesaj pentru a-l deschide din nou
-1 day ago;acum 1 zi
-is typing;scrie...
-are typing;scriu...
-No domains available;Niciun domeniu disponibil
-Shared domains;Domenii partajate
-Shared domain;Domeniu partajat
-Shared domain successfully deleted;Domeniu partajat șters cu succes
-Shared domain successfully added;Domeniu partajat adăugat cu succes
-Domain name;Nume de domeniu
-DNS records for;Înregistrări DNS pentru
-Fetching dns records;Se preiau înregistrările DNS
-No dns records found;Nicio înregistrare DNS găsită
-Content;Conținut
-Priority;Prioritate
-Ttl;TTL (Timp de Viață)
-Enable cloudflare proxy;Activați proxy Cloudflare
-CF Proxy;Proxy CF
-days ago;zile în urmă
-Cancel;Anulare
-An unexpected error occurred;A apărut o eroare neașteptată
-Testy;Testy
-Error from cloudflare api;Eroare de la API Cloudflare
-Profile;Profil
-No subscription available;Nicio abonare disponibilă
-Buy;Cumpără
-Redirecting;Redirecționare
-Apply;Aplică
-Applying code;Se aplică codul
-Invalid subscription code;Cod de abonament invalid
-Cancel Subscription;Anulează abonamentul
-Active until;Activ până la
-We will send you a notification upon subscription expiration;Vă vom trimite o notificare la expirarea abonamentului
-This token has been already used;Acest token a fost deja utilizat
-New login for;Autentificare nouă pentru
-No records found for this day;Nu s-au găsit înregistrări pentru această zi
-Change;Schimbă
-Changing;Se schimbă
-Minecraft version;Versiune Minecraft
-Build version;Versiune build
-Server installation is currently running;Instalarea serverului este în desfășurare în prezent
-Selected;Selectat
-Move deleted;Mutarea ștergerii
-Delete selected;Ștergeți selecția
-Log level;Nivelul de jurnal
-Log message;Mesaj de jurnal
-Time;Timp
-Version;Versiune
-You are running moonlight version;Rulați versiunea Moonlight
-Operating system;Sistem de operare
-Moonlight is running on;Moonlight rulează pe
-Memory usage;Utilizare memorie
-Moonlight is using;Moonlight folosește
-of memory;din memorie
-Cpu usage;Utilizare CPU
-Refresh;Reîmprospătează
-Send a message to all users;Trimiteți un mesaj tuturor utilizatorilor
-IP;IP
-URL;URL
-Device;Dispozitiv
-Change url;Schimbați URL-ul
-Message;Mesaj
-Enter message;Introduceți mesajul
-Enter the message to send;Introduceți mesajul de trimis
-Confirm;Confirmă
-Are you sure?;Sunteți sigur?
-Enter url;Introduceți URL-ul
-An unknown error occured while starting backup deletion;A apărut o eroare necunoscută în timpul începerii ștergerii backup-ului
-Success;Succes
-Backup URL successfully copied to your clipboard;URL-ul de backup a fost copiat cu succes în clipboard
-Backup restore started;Restaurarea backup-ului a început
-Backup successfully restored;Backup-ul a fost restaurat cu succes
-Register for;Înregistrare pentru
-Core;Nucleu
-Logs;Jurnale
-AuditLog;Jurnal de audit
-SecurityLog;Jurnal de securitate
-ErrorLog;Jurnal de eroare
-Resources;Resurse
-WinSCP cannot be launched here;WinSCP nu poate fi lansat aici
-Create a new folder;Creați un folder nou
-Enter a name;Introduceți un nume
-File upload complete;Încărcarea fișierului a fost finalizată
-New server;Server nou
-Sessions;Sesiuni
-New user;Utilizator nou
-Created at;Creat la
-Mail template not found;Șablonul de e-mail nu a fost găsit
-Missing admin permissions. This attempt has been logged ;Lipsesc permisiunile de administrator. Acest încercare a fost înregistrată
-Address;Adresă
-City;Oraș
-State;Stat
-Country;Țară
-Totp;Totp
-Discord;Discord
-Subscription;Abonament
-None;Niciunul
-No user with this id found;Niciun utilizator cu această ID găsit
-Back to list;Înapoi la listă
-New domain;Domeniu nou
-Reset password;Resetare parolă
-Password reset;Resetare parolă
-Reset the password of your account;Resetați parola contului dvs.
-Wrong here?;Greșit aici?
-A user with this email can not be found;Un utilizator cu această adresă de e-mail nu poate fi găsit
-Password reset successfull. Check your mail;Resetarea parolei a fost efectuată cu succes. Verificați-vă poșta
-Discord bot;Bot Discord
-New image;Imagine nouă
-Description;Descriere
-Uuid;UUID
-Enter tag name;Introduceți numele etichetei
-Remove;Eliminați
-No tags found;Nu s-au găsit etichete
-Enter docker image name;Introduceți numele imaginii Docker
-Tags;Etichete
-Docker images;Imagini Docker
-Default image;Imagine implicită
-Startup command;Comandă de pornire
-Install container;Instalați containerul
-Install entry;Intrare de instalare
-Configuration files;Fișiere de configurare
-Startup detection;Detectare de pornire
-Stop command;Comandă de oprire
-Successfully saved image;Imaginea a fost salvată cu succes
-No docker images found;Nu s-au găsit imagini Docker
-Key;Cheie
-Default value;Valoare implicită
-Allocations;Alocări
-No variables found;Nu s-au găsit variabile
-Successfully added image;Imaginea a fost adăugată cu succes
-Password change for;Schimbarea parolei pentru
-of;al
-New node;Nod nou
-Fqdn;Nume de domeniu complet
-Cores used;Nuclee utilizate
-used;folosite
-5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64
-Host system information;Informații despre sistemul gazdă
-0;0
-Docker containers running;Containere Docker în execuție
-details;Detalii
-1;1
-2;2
-DDos;DDoS
-No ddos attacks found;Nu s-au găsit atacuri DDoS
-Node;Nod
-Date;Dată
-DDos attack started;Atac DDoS început
-packets;pachete
-DDos attack stopped;Atac DDoS oprit
-packets; pachete
-Stop all;Oprește totul
-Kill all;Omoară totul
-Network in;Rețea intrare
-Network out;Rețea ieșire
-Kill all servers;Omoară toate serverele
-Do you really want to kill all running servers?;Doriți cu adevărat să opriți toate serverele care rulează?
-Change power state for;Schimbați starea de alimentare pentru
-to;la
-Stop all servers;Oprește toate serverele
-Do you really want to stop all running servers?;Doriți cu adevărat să opriți toate serverele care rulează?
-Manage ;Gestionează
-Manage user ;Gestionează utilizatorul
-Reloading;Se reîncarcă...
-Update;Actualizare
-Updating;Se actualizează
-Successfully updated user;Utilizator actualizat cu succes
-Discord id;ID Discord
-Discord username;Nume de utilizator Discord
-Discord discriminator;Discord Discriminator
-The Name field is required.;Câmpul Nume este obligatoriu.
-An error occured while logging you in;A apărut o eroare în timpul autentificării
-You need to enter an email address;Trebuie să introduceți o adresă de email
-You need to enter a password;Trebuie să introduceți o parolă
-You need to enter a password with minimum 8 characters in lenght;Trebuie să introduceți o parolă cu minim 8 caractere în lungime
-Proccessing;Se procesează...
-The FirstName field is required.;Câmpul Prenume este obligatoriu.
-The LastName field is required.;Câmpul Nume este obligatoriu.
-The Address field is required.;Câmpul Adresă este obligatoriu.
-The City field is required.;Câmpul Oraș este obligatoriu.
-The State field is required.;Câmpul Stat este obligatoriu.
-The Country field is required.;Câmpul Țară este obligatoriu.
-Street and house number requered;Strada și numărul casei sunt necesare.
-Max lenght reached;A fost atinsă lungimea maximă
-Server;Server
-stopped;oprit
-Cleanups;Curățări
-executed;executat
-Used clanup;Curățare folosită
-Enable;Activare
-Disable;Dezactivare
-Addons;Add-on-uri
-Javascript version;Versiune JavaScript
-Javascript file;Fișier JavaScript
-Select javascript file to execute on start;Selectați fișierul JavaScript pentru a fi executat la pornire
-Submit;Trimite
-Processing;Se procesează...
-Go up;Urcați în sus
-Running cleanup;Curățarea în curs de rulare
-servers;servere
-Select folder to move the file(s) to;Selectați folderul în care să mutați fișierele
-Paper version;Versiune tipărită
-Join2Start;Join2Start
-Server reset;Resetare server
-Reset;Resetare
-Resetting;Se resetează...
-Are you sure you want to reset this server?;Sigur doriți să resetați acest server?
-Are you sure? This cannot be undone;Sunteți sigur? Aceasta nu poate fi anulată
-Resetting server;Se resetează serverul...
-Deleted file;Fișier șters
-Reinstalling server;Se reinstalează serverul
-Uploading files;Încărcare fișiere
-complete;complet
-Upload complete;Încărcare completă
-Security;Securitate
-Subscriptions;Abonamente
-2fa Code;Cod 2FA
-Your account is secured with 2fa;Contul dvs. este securizat cu 2FA
-anyone write a fancy text here?;cineva să scrie un text frumos aici?
-Activate 2fa;Activare 2FA
-2fa apps;Aplicații 2FA
-Use an app like ;Utilizați o aplicație precum
-or;sau
-and scan the following QR Code;și scanați codul QR următor
-If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:;Dacă întâmpinați probleme la utilizarea codului QR, selectați introducerea manuală în aplicație și introduceți adresa dvs. de email și următorul cod:
-Finish activation;Finalizare activare
-2fa Code requiered;Cod 2FA necesar
-New password;Parolă nouă
-Secure your account;Securizați contul dvs.
-2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.;2FA adaugă un alt nivel de securitate contului dvs. Trebuie să introduceți un cod cu 6 cifre pentru a vă autentifica.
-New subscription;Abonament nou
-You need to enter a name;Trebuie să introduceți un nume
-You need to enter a description;Trebuie să introduceți o descriere
-Add new limit;Adăugați o limită nouă
-Create subscription;Creați abonament
-Options;Opțiuni
-Amount;Suma
-Do you really want to delete it?;Sigur doriți să ștergeți acesta?
-Loading your subscription;Se încarcă abonamentul dvs.
-Searching for deploy node;Căutare nod de implementare
-Searching for available images;Căutare imagini disponibile
-Server details;Detalii server
-Configure your server;Configurați serverul dvs.
-Default;Implicit
-You reached the maximum amount of servers for every image of your subscription;Ați atins cantitatea maximă de servere pentru fiecare imagine din abonamentul dvs.
-Personal information;Informații personale
-Enter code;Introduceți codul
-Server rename;Redenumire server
-Create code;Creați codul
-Save subscription;Salvați abonamentul
-Enter your information;Introduceți informațiile dvs.
-You need to enter your full name in order to use moonlight;Trebuie să introduceți numele complet pentru a utiliza Moonlight
-No node found;Niciun nod găsit
-No node found to deploy to found;Nu s-a găsit niciun nod pentru a fi implementat
-Node offline;Nodul este deconectat
-The node the server is running on is currently offline;Nodul pe care rulează serverul este momentan deconectat
-Server not found;Serverul nu a fost găsit
-A server with that id cannot be found or you have no access for this server;Un server cu această ID nu poate fi găsit sau nu aveți acces la acest server
-Compress;Comprimare
-Decompress;Dezcompresare
-Moving;Mutare...
-Compressing;Se comprimă...
-selected;selectat
-New website;Site web nou
-Plesk servers;Servere Plesk
-Base domain;Domeniu de bază
-Plesk server;Server Plesk
-Ftp;FTP
-No SSL certificate found;Nu s-a găsit niciun certificat SSL
-Ftp Host;Gazdă FTP
-Ftp Port;Port FTP
-Ftp Username;Nume utilizator FTP
-Ftp Password;Parolă FTP
-Use;Utilizați
-SSL Certificates;Certificate SSL
-SSL certificates;Certificate SSL
-Issue certificate;Emitere certificat
-New plesk server;Un nou server Plesk
-Api url;URL API
-Host system offline;Sistemul gazdă este offline
-The host system the website is running on is currently offline;Sistemul gazdă pe care rulează site-ul este în prezent offline
-No SSL certificates found;Nu s-au găsit certificate SSL
-No databases found for this website;Nu s-au găsit baze de date pentru acest site web
-The name should be at least 8 characters long;Numele trebuie să aibă cel puțin 8 caractere
-The name should only contain of lower case characters and numbers;Numele ar trebui să conțină doar litere mici și cifre
-Error from plesk;Eroare de la Plesk
-Host;Gazdă
-Username;Nume de utilizator
-SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one;Înregistrările SRV nu pot fi actualizate datorită clientului API Cloudflare. Vă rugăm să ștergeți înregistrarea și să creați una nouă
-The User field is required.;Câmpul Utilizator este obligatoriu
-You need to specify an owner;Trebuie să specificați un proprietar
-You need to specify an image;Trebuie să specificați o imagine
-Api Url;URL API
-Api Key;Cheie API
-Duration;Durată
-Enter duration of subscription;Introduceți durata abonamentului
-Copied code to clipboard;Codul a fost copiat în clipboard
-Invalid or expired subscription code;Cod de abonament invalid sau expirat
-Current subscription;Abonament curent
-You need to specify a server image;Trebuie să specificați o imagine de server
-CPU;CPU
-Hour;Oră
-Day;Zi
-Month;Lună
-Year;An
-All time;Tot timpul
-This function is not implemented;Această funcție nu este implementată
-Domain details;Detalii domeniu
-Configure your domain;Configurați domeniul dvs.
-You reached the maximum amount of domains in your subscription;Ați atins numărul maxim de domenii în abonamentul dvs.
-You need to specify a shared domain;Trebuie să specificați un domeniu partajat
-A domain with this name already exists for this shared domain;Un domeniu cu acest nume există deja pentru acest domeniu partajat
-The Email field is required.;Câmpul Email este obligatoriu
-The Password field is required.;Câmpul Parolă este obligatoriu
-The ConfirmPassword field is required.;Câmpul Confirmare Parolă este obligatoriu
-Passwords need to match;Parolele trebuie să se potrivească
-Cleanup exception;Excepție la curățare
-No shared domain found;Nu s-a găsit niciun domeniu partajat
-Searching for deploy plesk server;Se caută pentru a implementa serverul Plesk
-No plesk server found;Nu s-a găsit niciun server Plesk
-No plesk server found to deploy to;Nu s-a găsit niciun server Plesk pentru a implementa
-No node found to deploy to;Nu s-a găsit niciun nod pentru a implementa
-Website details;Detalii site web
-Configure your website;Configurați site-ul dvs. web
-The name cannot be longer that 32 characters;Numele nu poate avea mai mult de 32 de caractere
-The name should only consist of lower case characters;Numele ar trebui să conțină doar litere mici
-News;Știri
-Title...;Titlu...
-Enter text...;Introduceți text...
-Saving...;Se salvează...
-Deleting...;Se șterge...
-Delete post;Ștergeți postarea
-Do you really want to delete the post ";Doriți cu adevărat să ștergeți postarea "
-You have no domains;Nu aveți domenii
-We were not able to find any domains associated with your account;Nu am putut găsi niciun domeniu asociat cu contul dvs.
-You have no websites;Nu aveți site-uri web
-We were not able to find any websites associated with your account;Nu am putut găsi niciun site web asociat cu contul dvs.
-Guest;Vizitator
-You need a domain;Aveți nevoie de un domeniu
-New post;Postare nouă
-New entry;Intrare nouă
-You have no servers;Nu aveți servere
-We were not able to find any servers associated with your account;Nu am putut găsi niciun server asociat cu contul dvs.
-Error creating server on wings;Eroare la crearea serverului pe Wings
-An unknown error occurred while restoring a backup;A apărut o eroare necunoscută în timpul restaurării unei copii de rezervă
-Error from daemon;Eroare de la daemon
-End;Sfârșit
-Cloud panel;Panou de control pentru cloud
-Cloud panels;Panouri de control pentru cloud
-New cloud panel;Panou de control pentru cloud nou
-You need to enter an api key;Trebuie să introduceți o cheie API
-Webspaces;Spații web
-New webspace;Spațiu web nou
-The uploaded file should not be bigger than 100MB;Fișierul încărcat nu trebuie să depășească 100MB
-An unknown error occurred while uploading a file;A apărut o eroare necunoscută în timpul încărcării unui fișier
-No databases found for this webspace;Nu s-au găsit baze de date pentru acest spațiu web
-Sftp;SFTP
-Sftp Host;Gazdă SFTP
-Sftp Port;Port SFTP
-Sftp Username;Nume de utilizator SFTP
-Sftp Password;Parolă SFTP
-Lets Encrypt certificate successfully issued;Certificatul Lets Encrypt a fost emis cu succes
-Add shared domain;Adăugați domeniu partajat
-Webspace;Spațiu web
-You reached the maximum amount of websites in your subscription;Ați atins numărul maxim de site-uri web în abonamentul dvs.
-Searching for deploy web host;Se caută pentru a implementa gazda web
-Webspace details;Detalii spațiu web
-Web host;Gazdă web
-Configure your webspaces;Configurați spațiile dvs. web
-You reached the maximum amount of webspaces in your subscription;Ați atins numărul maxim de spații web în abonamentul dvs.
-Create a webspace;Creați un spațiu web
-Manage your webspaces;Gestionați spațiile dvs. web
-Modify the content of your webspaces;Modificați conținutul spațiilor dvs. web
-Successfully updated password;Parola a fost actualizată cu succes
-An unknown error occurred while sending your message;A apărut o eroare necunoscută în timpul trimiterii mesajului dvs.
-Open chats;Deschideți conversații
-No message sent yet;Nu ați trimis încă niciun mesaj
-Support ticket open;Bilet de suport deschis
-Support ticket closed;Bilet de suport închis
-Your connection has been paused;Conexiunea dvs. a fost pusă în pauză
-We paused your connection because of inactivity. To resume, simply focus the tab and wait a few seconds;Am pus în pauză conexiunea dvs. din cauza inactivității. Pentru a relua, focalizați pur și simplu fila și așteptați câteva secunde
-Failed to reconnect to the moonlight servers;Nu s-a reușit reconectarea la serverele Moonlight
-We were unable to reconnect to moonlight. Please refresh the page;Nu am reușit să ne reconectăm la Moonlight. Vă rugăm să reîmprospătați pagina
-Failed to reconnect to the moonlight servers. The connection has been rejected;Nu s-a reușit reconectarea la serverele Moonlight. Conexiunea a fost respinsă
-We were unable to reconnect to moonlight. Most of the time this is caused by an update of moonlight. Please refresh the page;Nu am reușit să ne reconectăm la Moonlight. De cele mai multe ori, aceasta este cauzată de o actualizare a Moonlight. Vă rugăm să reîmprospătați pagina
-Verifying token, loading user data;Se verifică token-ul, se încarcă datele utilizatorului
-Reload config;Reîncarcă configurația
-Successfully reloading configuration;Configurația a fost reîncărcată cu succes
-Successfully reloaded configuration;Configurația a fost reîncărcată cu succes
-Flows;Fluxuri
-Add node;Adăugați nod
-Web system;Sistem web
-Servers with this image;Servere cu această imagine
-You need to specify a user;Trebuie să specificați un utilizator
-Import;Importați
-Export;Exportați
-Exporting;Se exportă
-Successfully imported image;Imaginea a fost importată cu succes
-Forge version;Versiune Forge
-Fabric version;Versiune Fabric
-Fabric loader version;Versiune Fabric Loader
-Rate;Evaluare
-Hey, can i borrow you for a second?;Hey, pot să te împrumut pentru o secundă?
-We want to improve our services and get a little bit of feedback how we are currently doing. Please leave us a rating;Vrem să îmbunătățim serviciile noastre și să obținem un pic de feedback despre modul în care facem în prezent. Vă rugăm să ne lăsați o evaluare
-Thanks for your rating;Mulțumim pentru evaluarea dvs.
-It would be really kind of you rating us on a external platform as it will help our project very much;Ar fi foarte amabil din partea dvs. să ne evaluați pe o platformă externă, deoarece va ajuta foarte mult proiectul nostru
-Close;Închide
-Rating saved;Evaluare salvată
-Group;Grup
-Beta;Beta
-Create a new group;Creați un grup nou
-Download WinSCP;Descărcați WinSCP
-Show connection details;Afișați detaliile de conexiune
-New;Nou
-New file;Fișier nou
-Connection details;Detalii de conexiune
-Malware;Program malware
-Create a new file;Creați un fișier nou
-Edit layout;Editați aspectul
-Uptime;Timp de funcționare
-Moonlight is online since;Moonlight este online de la
-User;Utilizator
-Databases;Baze de date
-Sesiuni;Sesiuni
-Active users;Utilizatori activi
-Search for plugins;Căutați plugin-uri
-Search;Căutare
-Searching;Se caută...
-Successfully installed gunshell;Gunshell a fost instalat cu succes
-Successfully installed fastasyncworldedit;FastAsyncWorldEdit a fost instalat cu succes
-Successfully installed minimotd;MiniMotd a fost instalat cu succes
-Moonlight health;Stare Moonlight
-Healthy;Sănătos
-Successfully saved file;Fișier salvat cu succes
-Unsorted servers;Servere nesortate
-Enter a new name;Introduceți un nume nou
-Sign in with;Conectare cu
-Make your services accessible through your own domain;Faceți serviciile dvs. accesibile prin propriul domeniu
-New group;Grup nou
-Finish editing layout;Finalizați editarea aspectului
-Remove group;Eliminați grupul
-Hidden in edit mode;Ascuns în modul de editare
-Enter your 2fa code here;Introduceți codul dvs. 2FA aici
-Two factor authentication;Autentificare în două factori
-Preferences;Preferințe
-Streamer mode;Mod streamer
-Scan the QR code and enter the code generated by the app you have scanned it in;Scanați codul QR și introduceți codul generat de aplicație
-Start scan;Începeți scanarea
-Results;Rezultate
-Scan in progress;Scanare în curs
-Debug;Depanare
-Save changes;Salvați modificările
-Delete domain;Ștergeți domeniul
-Python version;Versiune Python
-Python file;Fișier Python
-Select python file to execute on start;Selectați fișierul Python pentru a fi executat la pornire
-You have no webspaces;Nu aveți spații web
-We were not able to find any webspaces associated with your account;Nu am reușit să găsim spații web asociate contului dvs.
-Backup download successfully started;Descărcarea backup-ului a început cu succes
-Error from cloud panel;Eroare din panoul de control al norului
-Error from wings;Eroare din Wings
-Remove link;Elimină linkul
-Your account is linked to a discord account;Contul dvs. este legat de un cont Discord
-You are able to use features like the discord bot of moonlight;Puteți utiliza funcții precum botul Discord al Moonlight
-The password should be at least 8 characters long;Parola trebuie să aibă cel puțin 8 caractere
-The name should only consist of lower case characters or numbers;Numele ar trebui să conțină doar litere mici sau cifre
-The requested resource was not found;Resursa cerută nu a fost găsită
-We were not able to find the requested resource. This can have following reasons;Nu am putut găsi resursa solicitată. Acest lucru poate avea următoarele motive
-The resource was deleted;Resursa a fost ștearsă
-You have to permission to access this resource;Nu aveți permisiunea de a accesa această resursă
-You may have entered invalid data;Ați putut introduce date invalide
-A unknown bug occured;A apărut o eroare necunoscută
-An api was down and not proper handled;O interfață API a fost indisponibilă și nu a fost gestionată corespunzător
-A database with this name does already exist;O bază de date cu acest nume există deja
-Successfully installed quickshop-hikari;Quickshop-Hikari a fost instalat cu succes
-You need to enter a valid domain;Trebuie să introduceți un domeniu valid
-2fa code;Cod 2FA
-This feature is not available for;Această funcționalitate nu este disponibilă pentru
-Your account is currently not linked to discord;Contul dvs. nu este în prezent legat de Discord
-To use features like the discord bot, link your moonlight account with your discord account;Pentru a utiliza funcții precum botul Discord, legați-vă contul Moonlight de contul Discord
-Link account;Conectează contul
-Continue;Continuați
-Preparing;Pregătire
-Make sure you have installed one of the following apps on your smartphone and press continue;Asigurați-vă că ați instalat una dintre următoarele aplicații pe smartphone-ul dvs. și apăsați Continuare
-The max length for the name is 32 characters;Lungimea maximă a numelui este de 32 de caractere
-Successfully installed chunky;Chunky a fost instalat cu succes
-Successfully installed huskhomes;Huskhomes a fost instalat cu succes
-Successfully installed simply-farming;Simply-Farming a fost instalat cu succes
-You need to specify a first name;Trebuie să specificați un prenume
-You need to specify a last name;Trebuie să specificați un nume de familie
-You need to specify a password;Trebuie să specificați o parolă
-Please solve the captcha;Vă rugăm să rezolvați captcha
-The email is already in use;Adresa de email este deja folosită
-The dns records of your webspace do not point to the host system;Înregistrările DNS ale spațiului dvs. web nu se îndreaptă către sistemul gazdă
-Scan complete;Scanare completă
-Currently scanning:;În prezent se scanează:
-Successfully installed dynmap;Dynmap a fost instalat cu succes
-Successfully installed squaremap;Squaremap a fost instalat cu succes
-No web host found;Niciun gazdă web găsită
-No web host found to deploy to;Niciun gazdă web găsită pentru a implementa
-Successfully installed sleeper;Sleeper a fost instalat cu succes
-You need to enter a domain;Trebuie să introduceți un domeniu
-Enter a ip;Introduceți o adresă IP
-Ip Bans;Interdicții IP
-Ip;Adresă IP
-Successfully installed simple-voice-chat;Simple-Voice-Chat a fost instalat cu succes
-Successfully installed smithing-table-fix;Smithing-Table-Fix a fost instalat cu succes
-Successfully installed justplayer-tpa;Justplayer-TPA a fost instalat cu succes
-Successfully installed ishop;iShop a fost instalat cu succes
-Successfully installed lifestealre;Lifestealre a fost instalat cu succes
-Successfully installed lifeswap;Lifeswap a fost instalat cu succes
-Java version;Versiune Java
-Jar file;Fișier JAR
-Select jar to execute on start;Selectați fișierul JAR pentru a fi executat la pornire
-A website with this domain does already exist;Un website cu acest domeniu există deja
-Successfully installed discordsrv;DiscordSrv a fost instalat cu succes
-Reinstall;Reinstalați
-Reinstalling;Se reinstalează
-Successfully installed freedomchat;Freedomchat a fost instalat cu succes
-Leave empty for the default background image;Lăsați gol pentru imaginea de fundal implicită
-Background image url;URL-ul imaginii de fundal
-of CPU used;de CPU folosit
-memory used;memorie folosită
-163;163
-172;172
-Sentry;Sentry
-Sentry is enabled;Sentry este activat
-Successfully installed mobis-homes;Mobis-Homes a fost instalat cu succes
-Your moonlight account is disabled;Contul Moonlight este dezactivat
-Your moonlight account is currently disabled. But dont worry your data is still saved;Contul Moonlight este în prezent dezactivat. Dar nu vă faceți griji, datele dvs. sunt încă salvate
-You need to specify a email address;Trebuie să specificați o adresă de email
-A user with that email does already exist;Un utilizator cu această adresă de email există deja
-Successfully installed buildmode;Buildmode a fost instalat cu succes
-Successfully installed plasmo-voice;Plasmo-Voice a fost instalat cu succes
-157;157
-174;174
-158;158
-Webspace not found;Spațiu web nu găsit
-A webspace with that id cannot be found or you have no access for this webspace;Un spațiu web cu această ID nu poate fi găsit sau nu aveți acces la acest spațiu web
-No plugin download for your minecraft version found;Nu s-a găsit nicio descărcare de plugin pentru versiunea dvs. Minecraft
-Successfully installed gamemode-alias;Gamemode-Alias a fost instalat cu succes
-228;228
-User;Utilizator
-Send notification;Trimite notificare
-Successfully saved changes;Modificări salvate cu succes
-Archiving;Se arhivează
-Server is currently not archived;Serverul nu este arhivat în prezent
-Add allocation;Adaugă alocare
-231;231
-175;175
-Dotnet version;Versiune Dotnet
-Dll file;Fișier DLL
-Select dll to execute on start;Selectați fișierul DLL pentru a fi executat la pornire

+ 0 - 53
Moonlight/defaultstorage/resources/mail/login.html

@@ -1,53 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>New moonlight login</title>
-</head>
-<body>
-<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
-    <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto"
-           style="border-collapse:collapse">
-        <tbody>
-        <tr>
-            <td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
-                <div style="text-align:center; margin:0 15px 34px 15px">
-                    <div style="margin-bottom: 10px">
-                        <a href="https://endelon-hosting.de" rel="noopener" target="_blank">
-                            <img alt="Logo" src="https://moonlight.endelon-hosting.de/assets/media/logo/MoonFullText.png" style="height: 35px">
-                        </a>
-                    </div>
-                    <div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
-                        <p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">Hey {{FirstName}}, there is a new login in your moonlight account</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Here is all the data we collected</p>
-                        <p style="margin-bottom:2px; color:#7E8299">IP: {{Ip}}</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Device: {{Device}}</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Location: {{Location}}</p>
-                    </div>
-                    <a href="https://moonlight.endelon-hosting.de" target="_blank"
-                       style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500;">Open Moonlight
-                    </a>
-                </div>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
-                <p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">You need help?</p>
-                <p style="margin-bottom:2px">We are happy to help!</p>
-                <p style="margin-bottom:4px">More information at
-                    <a href="https://endelon.link/support" rel="noopener" target="_blank" style="font-weight: 600">endelon.link/support</a>.
-                </p>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
-                <p>Copyright 2022 Endelon Hosting </p>
-            </td>
-        </tr>
-        </tbody>
-    </table>
-</div>
-</body>
-</html>

+ 0 - 53
Moonlight/defaultstorage/resources/mail/passwordChange.html

@@ -1,53 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Moonlight password change</title>
-</head>
-<body>
-<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
-    <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto"
-           style="border-collapse:collapse">
-        <tbody>
-        <tr>
-            <td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
-                <div style="text-align:center; margin:0 15px 34px 15px">
-                    <div style="margin-bottom: 10px">
-                        <a href="https://endelon-hosting.de" rel="noopener" target="_blank">
-                            <img alt="Logo" src="https://moonlight.endelon-hosting.de/assets/media/logo/MoonFullText.png" style="height: 35px">
-                        </a>
-                    </div>
-                    <div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
-                        <p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">Hey {{FirstName}}, your password has been changed</p>
-                        <p style="margin-bottom:2px; color:#7E8299">If this was not you please contact us. Also here is the data we collected.</p>
-                        <p style="margin-bottom:2px; color:#7E8299">IP: {{Ip}}</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Device: {{Device}}</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Location: {{Location}}</p>
-                    </div>
-                    <a href="https://moonlight.endelon-hosting.de" target="_blank"
-                       style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500;">Open Moonlight
-                    </a>
-                </div>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
-                <p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">You need help?</p>
-                <p style="margin-bottom:2px">We are happy to help!</p>
-                <p style="margin-bottom:4px">More information at
-                    <a href="https://endelon.link/support" rel="noopener" target="_blank" style="font-weight: 600">endelon.link/support</a>.
-                </p>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
-                <p>Copyright 2023 Endelon Hosting </p>
-            </td>
-        </tr>
-        </tbody>
-    </table>
-</div>
-</body>
-</html>

+ 0 - 54
Moonlight/defaultstorage/resources/mail/passwordReset.html

@@ -1,54 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Moonlight password reset</title>
-</head>
-<body>
-<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
-    <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto"
-           style="border-collapse:collapse">
-        <tbody>
-        <tr>
-            <td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
-                <div style="text-align:center; margin:0 15px 34px 15px">
-                    <div style="margin-bottom: 10px">
-                        <a href="https://endelon-hosting.de" rel="noopener" target="_blank">
-                            <img alt="Logo" src="https://moonlight.endelon-hosting.de/assets/media/logo/MoonFullText.png" style="height: 35px">
-                        </a>
-                    </div>
-                    <div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
-                        <p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">Hey {{FirstName}}, your password has been resetted</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Your new password is: <b>{{Password}}</b></p>
-                        <p style="margin-bottom:2px; color:#7E8299">If this was not you please contact us. Also here is the data we collected.</p>
-                        <p style="margin-bottom:2px; color:#7E8299">IP: {{Ip}}</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Device: {{Device}}</p>
-                        <p style="margin-bottom:2px; color:#7E8299">Location: {{Location}}</p>
-                    </div>
-                    <a href="https://moonlight.endelon-hosting.de" target="_blank"
-                       style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500;">Open Moonlight
-                    </a>
-                </div>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
-                <p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">You need help?</p>
-                <p style="margin-bottom:2px">We are happy to help!</p>
-                <p style="margin-bottom:4px">More information at
-                    <a href="https://endelon.link/support" rel="noopener" target="_blank" style="font-weight: 600">endelon.link/support</a>.
-                </p>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
-                <p>Copyright 2022 Endelon Hosting </p>
-            </td>
-        </tr>
-        </tbody>
-    </table>
-</div>
-</body>
-</html>

+ 0 - 50
Moonlight/defaultstorage/resources/mail/register.html

@@ -1,50 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Welcome</title>
-</head>
-<body>
-<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
-    <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto"
-           style="border-collapse:collapse">
-        <tbody>
-        <tr>
-            <td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
-                <div style="text-align:center; margin:0 15px 34px 15px">
-                    <div style="margin-bottom: 10px">
-                        <a href="https://endelon-hosting.de" rel="noopener" target="_blank">
-                            <img alt="Logo" src="https://moonlight.endelon-hosting.de/assets/media/logo/MoonFullText.png" style="height: 35px">
-                        </a>
-                    </div>
-                    <div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
-                        <p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">Hey {{FirstName}}, welcome to moonlight</p>
-                        <p style="margin-bottom:2px; color:#7E8299">We are happy to welcome you in ;)</p>
-                    </div>
-                    <a href="https://moonlight.endelon-hosting.de" target="_blank"
-                       style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500;">Open Moonlight
-                    </a>
-                </div>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
-                <p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">You need help?</p>
-                <p style="margin-bottom:2px">We are happy to help!</p>
-                <p style="margin-bottom:4px">More information at
-                    <a href="https://endelon.link/support" rel="noopener" target="_blank" style="font-weight: 600">endelon.link/support</a>.
-                </p>
-            </td>
-        </tr>
-        <tr>
-            <td align="center" valign="center"
-                style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
-                <p>Copyright 2022 Endelon Hosting </p>
-            </td>
-        </tr>
-        </tbody>
-    </table>
-</div>
-</body>
-</html>

BIN
Moonlight/defaultstorage/resources/public/background/main.jpg


+ 0 - 14
Moonlight/defaultstorage/resources/public/images/logo.svg

@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="256px" height="301px" viewBox="0 0 256 301" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
-    <defs>
-        <linearGradient x1="2.17771739%" y1="34.7938955%" x2="92.7221942%" y2="91.3419405%" id="linearGradient-1">
-            <stop stop-color="#41A7EF" offset="0%"></stop>
-            <stop stop-color="#813DDE" offset="54.2186236%"></stop>
-            <stop stop-color="#8F2EE2" offset="74.4988788%"></stop>
-            <stop stop-color="#A11CE6" offset="100%"></stop>
-        </linearGradient>
-    </defs>
-    <g>
-        <path d="M124.183681,101.699 C124.183681,66.515 136.256681,34.152 156.486681,8.525 C159.197681,5.092 156.787681,0.069 152.412681,0.012 C151.775681,0.004 151.136681,0 150.497681,0 C67.6206813,0 0.390681343,66.99 0.00168134279,149.775 C-0.386318657,232.369 66.4286813,300.195 149.019681,300.988 C189.884681,301.381 227.036681,285.484 254.376681,259.395 C257.519681,256.396 255.841681,251.082 251.548681,250.42 C179.413681,239.291 124.183681,176.949 124.183681,101.699" fill="url(#linearGradient-1)"></path>
-    </g>
-</svg>

BIN
Moonlight/defaultstorage/resources/public/images/logolong.png


+ 0 - 9
Moonlight/wwwroot/assets/css/snow.css

@@ -1,9 +0,0 @@
-.snow-canvas {
-    position: fixed;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    z-index: -1;
-    pointer-events: none;
-}

+ 372 - 0
Moonlight/wwwroot/assets/css/toastr.css

@@ -0,0 +1,372 @@
+.toastr-title {
+    font-weight: bold;
+}
+
+.toastr-message {
+    -ms-word-wrap: break-word;
+    word-wrap: break-word;
+}
+
+.toastr-message a,
+.toastr-message label {
+    color: #FFFFFF;
+}
+
+.toastr-message a:hover {
+    color: #CCCCCC;
+    text-decoration: none;
+}
+
+.toastr-close-button {
+    position: relative;
+    right: -0.3em;
+    top: -0.3em;
+    float: right;
+    font-size: 20px;
+    font-weight: bold;
+    color: #FFFFFF;
+    -webkit-text-shadow: 0 1px 0 #ffffff;
+    text-shadow: 0 1px 0 #ffffff;
+    opacity: 0.8;
+    -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
+    filter: alpha(opacity=80);
+    line-height: 1;
+}
+
+.toastr-close-button:hover,
+.toastr-close-button:focus {
+    color: #000000;
+    text-decoration: none;
+    cursor: pointer;
+    opacity: 0.4;
+    -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
+    filter: alpha(opacity=40);
+}
+
+.rtl .toastr-close-button {
+    left: -0.3em;
+    float: left;
+    right: 0.3em;
+}
+
+/*Additional properties for button version
+ iOS requires the button element instead of an anchor tag.
+ If you want the anchor version, it requires `href="#"`.*/
+button.toastr-close-button {
+    padding: 0;
+    cursor: pointer;
+    background: transparent;
+    border: 0;
+    -webkit-appearance: none;
+}
+
+.toastr-top-center {
+    top: 0;
+    right: 0;
+    width: 100%;
+}
+
+.toastr-top-center-nav-margin {
+    top: 65px;
+    right: 0;
+    width: 100%;
+}
+
+.toastr-bottom-center {
+    bottom: 0;
+    right: 0;
+    width: 100%;
+}
+
+.toastr-top-full-width {
+    top: 0;
+    right: 0;
+    width: 100%;
+}
+
+.toastr-top-full-width-nav-margin {
+    top: 65px;
+    right: 0;
+    width: 100%;
+}
+
+.toastr-bottom-full-width {
+    bottom: 0;
+    right: 0;
+    width: 100%;
+}
+
+.toastr-top-left {
+    top: 12px;
+    left: 12px;
+}
+
+.toastr-top-left-nav-margin {
+    top: 65px;
+    left: 12px;
+}
+
+.toastr-top-right {
+    top: 12px;
+    right: 12px;
+}
+
+.toastr-top-right-nav-margin {
+    top: 65px;
+    right: 12px;
+}
+
+.toastr-bottom-right {
+    right: 12px;
+    bottom: 12px;
+}
+
+.toastr-bottom-left {
+    bottom: 12px;
+    left: 12px;
+}
+
+#toastr-container {
+    position: fixed;
+    z-index: 999999;
+    pointer-events: none;
+    /*overrides*/
+}
+
+#toastr-container * {
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+}
+
+#toastr-container > div {
+    position: relative;
+    pointer-events: auto;
+    overflow: hidden;
+    margin: 0 0 6px;
+    padding: 15px 15px 15px 50px;
+    width: 300px;
+    -moz-border-radius: 3px 3px 3px 3px;
+    -webkit-border-radius: 3px 3px 3px 3px;
+    border-radius: 3px 3px 3px 3px;
+    background-position: 15px center;
+    background-repeat: no-repeat;
+    -moz-box-shadow: 0 0 12px #999999;
+    -webkit-box-shadow: 0 0 12px #999999;
+    box-shadow: 0 0 12px #999999;
+    color: #FFFFFF;
+    opacity: 0.8;
+    -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
+    filter: alpha(opacity=80);
+}
+
+#toastr-container > div.rtl {
+    direction: rtl;
+    padding: 15px 50px 15px 15px;
+    background-position: right 15px center;
+}
+
+#toastr-container > div:hover {
+    -moz-box-shadow: 0 0 12px #000000;
+    -webkit-box-shadow: 0 0 12px #000000;
+    box-shadow: 0 0 12px #000000;
+    opacity: 1;
+    -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+    filter: alpha(opacity=100);
+    cursor: pointer;
+}
+
+#toastr-container > .toastr-info {
+    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
+}
+
+#toastr-container > .toastr-error {
+    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
+}
+
+#toastr-container > .toastr-success {
+    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
+}
+
+#toastr-container > .toastr-warning {
+    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
+}
+
+#toastr-container.toastr-top-center-nav-margin > div,
+#toastr-container.toastr-top-center > div,
+#toastr-container.toastr-bottom-center > div {
+    width: 300px;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+#toastr-container.toastr-top-full-width-nav-margin > div,
+#toastr-container.toastr-top-full-width > div,
+#toastr-container.toastr-bottom-full-width > div {
+    width: 96%;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.toastr {
+    background-color: #030303;
+}
+
+.toastr-success {
+    background-color: #51A351;
+}
+
+.toastr-error {
+    background-color: #BD362F;
+}
+
+.toastr-info {
+    background-color: #2F96B4;
+}
+
+.toastr-warning {
+    background-color: #F89406;
+}
+
+.toastr-progress {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    height: 4px;
+    background-color: #000000;
+    opacity: 0.4;
+    -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
+    filter: alpha(opacity=40);
+}
+
+/*Responsive Design*/
+@media all and (max-width: 240px) {
+    #toastr-container > div {
+        padding: 8px 8px 8px 50px;
+        width: 11em;
+    }
+    #toastr-container > div.rtl {
+        padding: 8px 50px 8px 8px;
+    }
+    #toastr-container .toastr-close-button {
+        right: -0.2em;
+        top: -0.2em;
+    }
+    #toastr-container .rtl .toastr-close-button {
+        left: -0.2em;
+        right: 0.2em;
+    }
+}
+@media all and (min-width: 241px) and (max-width: 480px) {
+    #toastr-container > div {
+        padding: 8px 8px 8px 50px;
+        width: 18em;
+    }
+    #toastr-container > div.rtl {
+        padding: 8px 50px 8px 8px;
+    }
+    #toastr-container .toastr-close-button {
+        right: -0.2em;
+        top: -0.2em;
+    }
+    #toastr-container .rtl .toastr-close-button {
+        left: -0.2em;
+        right: 0.2em;
+    }
+}
+@media all and (min-width: 481px) and (max-width: 768px) {
+    #toastr-container > div {
+        padding: 15px 15px 15px 50px;
+        width: 25em;
+    }
+    #toastr-container > div.rtl {
+        padding: 15px 50px 15px 15px;
+    }
+}
+
+.toastr {
+    background-position: 1.5rem center /*rtl:calc(100% - 1.5rem) center*/ !important;
+    box-shadow: var(--kt-dropdown-box-shadow) !important;
+    border-radius: 0.475rem !important;
+    border: 0 !important;
+    background-color: var(--kt-gray-100);
+    color: var(--kt-gray-700);
+    padding: 1.25rem 1.25rem 1.25rem 4.5rem !important;
+}
+.toastr .toastr-close-button {
+    outline: none !important;
+    font-size: 0;
+    width: 0.85rem;
+    height: 0.85rem;
+}
+.toastr .toastr-title {
+    font-size: 1.15rem;
+    font-weight: 500;
+}
+.toastr .toastr-title + .toastr-message {
+    margin-top: 0.25rem;
+}
+.toastr .toastr-message {
+    font-size: 1rem;
+    font-weight: 400;
+}
+.toastr.toastr-success {
+    background-color: var(--kt-success);
+    color: var(--kt-success-inverse);
+}
+.toastr.toastr-success .toastr-close-button {
+    mask-repeat: no-repeat;
+    mask-position: center;
+    -webkit-mask-repeat: no-repeat;
+    -webkit-mask-position: center;
+    background-color: var(--kt-success-inverse);
+    -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-success-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+    mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-success-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+}
+.toastr.toastr-info {
+    background-color: var(--kt-info);
+    color: var(--kt-info-inverse);
+}
+.toastr.toastr-info .toastr-close-button {
+    mask-repeat: no-repeat;
+    mask-position: center;
+    -webkit-mask-repeat: no-repeat;
+    -webkit-mask-position: center;
+    background-color: var(--kt-info-inverse);
+    -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-info-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+    mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-info-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+}
+.toastr.toastr-warning {
+    background-color: var(--kt-warning);
+    color: var(--kt-warning-inverse);
+}
+.toastr.toastr-warning .toastr-close-button {
+    mask-repeat: no-repeat;
+    mask-position: center;
+    -webkit-mask-repeat: no-repeat;
+    -webkit-mask-position: center;
+    background-color: var(--kt-warning-inverse);
+    -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-warning-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+    mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-warning-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+}
+.toastr.toastr-error {
+    background-color: var(--kt-danger);
+    color: var(--kt-danger-inverse);
+}
+.toastr.toastr-error .toastr-close-button {
+    mask-repeat: no-repeat;
+    mask-position: center;
+    -webkit-mask-repeat: no-repeat;
+    -webkit-mask-position: center;
+    background-color: var(--kt-danger-inverse);
+    -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-danger-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+    mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--kt-danger-inverse%29'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e");
+}
+
+.toastr-top-center {
+    top: 12px;
+}
+
+.toastr-bottom-center {
+    bottom: 12px;
+}

Разница между файлами не показана из-за своего большого размера
+ 5 - 0
Moonlight/wwwroot/assets/js/apexcharts.js


Разница между файлами не показана из-за своего большого размера
+ 5 - 0
Moonlight/wwwroot/assets/js/bootstrap.min.js


+ 7522 - 0
Moonlight/wwwroot/assets/js/draggable.bundle.js

@@ -0,0 +1,7522 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define("Draggable", [], factory);
+	else if(typeof exports === 'object')
+		exports["Draggable"] = factory();
+	else
+		root["Draggable"] = factory();
+})(window, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// define __esModule on exports
+/******/ 	__webpack_require__.r = function(exports) {
+/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 		}
+/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
+/******/ 	};
+/******/
+/******/ 	// create a fake namespace object
+/******/ 	// mode & 1: value is a module id, require it
+/******/ 	// mode & 2: merge all properties of value into the ns
+/******/ 	// mode & 4: return value when already ns object
+/******/ 	// mode & 8|1: behave like require
+/******/ 	__webpack_require__.t = function(value, mode) {
+/******/ 		if(mode & 1) value = __webpack_require__(value);
+/******/ 		if(mode & 8) return value;
+/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ 		var ns = Object.create(null);
+/******/ 		__webpack_require__.r(ns);
+/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ 		return ns;
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 72);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _AbstractPlugin = __webpack_require__(66);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _AbstractPlugin2.default;
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _AbstractEvent = __webpack_require__(70);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _AbstractEvent2.default;
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _closest = __webpack_require__(57);
+
+Object.defineProperty(exports, 'closest', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_closest).default;
+  }
+});
+
+var _requestNextAnimationFrame = __webpack_require__(55);
+
+Object.defineProperty(exports, 'requestNextAnimationFrame', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_requestNextAnimationFrame).default;
+  }
+});
+
+var _distance = __webpack_require__(53);
+
+Object.defineProperty(exports, 'distance', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_distance).default;
+  }
+});
+
+var _touchCoords = __webpack_require__(51);
+
+Object.defineProperty(exports, 'touchCoords', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_touchCoords).default;
+  }
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _SensorEvent = __webpack_require__(46);
+
+Object.keys(_SensorEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SensorEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _Sensor = __webpack_require__(49);
+
+var _Sensor2 = _interopRequireDefault(_Sensor);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Sensor2.default;
+
+/***/ }),
+/* 5 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _DragEvent = __webpack_require__(14);
+
+Object.keys(_DragEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _DragEvent[key];
+    }
+  });
+});
+
+var _DraggableEvent = __webpack_require__(13);
+
+Object.keys(_DraggableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _DraggableEvent[key];
+    }
+  });
+});
+
+var _Plugins = __webpack_require__(12);
+
+Object.keys(_Plugins).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _Plugins[key];
+    }
+  });
+});
+
+var _Sensors = __webpack_require__(6);
+
+Object.keys(_Sensors).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _Sensors[key];
+    }
+  });
+});
+
+var _Draggable = __webpack_require__(39);
+
+var _Draggable2 = _interopRequireDefault(_Draggable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Draggable2.default;
+
+/***/ }),
+/* 6 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _Sensor = __webpack_require__(4);
+
+Object.defineProperty(exports, 'Sensor', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Sensor).default;
+  }
+});
+
+var _MouseSensor = __webpack_require__(48);
+
+Object.defineProperty(exports, 'MouseSensor', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_MouseSensor).default;
+  }
+});
+
+var _TouchSensor = __webpack_require__(45);
+
+Object.defineProperty(exports, 'TouchSensor', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_TouchSensor).default;
+  }
+});
+
+var _DragSensor = __webpack_require__(43);
+
+Object.defineProperty(exports, 'DragSensor', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_DragSensor).default;
+  }
+});
+
+var _ForceTouchSensor = __webpack_require__(41);
+
+Object.defineProperty(exports, 'ForceTouchSensor', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_ForceTouchSensor).default;
+  }
+});
+
+var _SensorEvent = __webpack_require__(3);
+
+Object.keys(_SensorEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SensorEvent[key];
+    }
+  });
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/***/ }),
+/* 7 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _SnappableEvent = __webpack_require__(20);
+
+Object.keys(_SnappableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SnappableEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 8 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _CollidableEvent = __webpack_require__(25);
+
+Object.keys(_CollidableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _CollidableEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 9 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _SortableEvent = __webpack_require__(29);
+
+Object.keys(_SortableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SortableEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 10 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _SwappableEvent = __webpack_require__(32);
+
+Object.keys(_SwappableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SwappableEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 11 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _DroppableEvent = __webpack_require__(35);
+
+Object.keys(_DroppableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _DroppableEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 12 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _Announcement = __webpack_require__(68);
+
+Object.defineProperty(exports, 'Announcement', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Announcement).default;
+  }
+});
+Object.defineProperty(exports, 'defaultAnnouncementOptions', {
+  enumerable: true,
+  get: function () {
+    return _Announcement.defaultOptions;
+  }
+});
+
+var _Focusable = __webpack_require__(65);
+
+Object.defineProperty(exports, 'Focusable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Focusable).default;
+  }
+});
+
+var _Mirror = __webpack_require__(63);
+
+Object.defineProperty(exports, 'Mirror', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Mirror).default;
+  }
+});
+Object.defineProperty(exports, 'defaultMirrorOptions', {
+  enumerable: true,
+  get: function () {
+    return _Mirror.defaultOptions;
+  }
+});
+
+var _Scrollable = __webpack_require__(59);
+
+Object.defineProperty(exports, 'Scrollable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Scrollable).default;
+  }
+});
+Object.defineProperty(exports, 'defaultScrollableOptions', {
+  enumerable: true,
+  get: function () {
+    return _Scrollable.defaultOptions;
+  }
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/***/ }),
+/* 13 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _DraggableEvent = __webpack_require__(69);
+
+Object.keys(_DraggableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _DraggableEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 14 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _DragEvent = __webpack_require__(71);
+
+Object.keys(_DragEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _DragEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 15 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onSortableSorted = Symbol('onSortableSorted');
+const onSortableSort = Symbol('onSortableSort');
+
+/**
+ * SortAnimation default options
+ * @property {Object} defaultOptions
+ * @property {Number} defaultOptions.duration
+ * @property {String} defaultOptions.easingFunction
+ * @type {Object}
+ */
+const defaultOptions = exports.defaultOptions = {
+  duration: 150,
+  easingFunction: 'ease-in-out'
+};
+
+/**
+ * SortAnimation plugin adds sort animation for sortable
+ * @class SortAnimation
+ * @module SortAnimation
+ * @extends AbstractPlugin
+ */
+class SortAnimation extends _AbstractPlugin2.default {
+  /**
+   * SortAnimation constructor.
+   * @constructs SortAnimation
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * SortAnimation options
+     * @property {Object} options
+     * @property {Number} defaultOptions.duration
+     * @property {String} defaultOptions.easingFunction
+     * @type {Object}
+     */
+    this.options = _extends({}, defaultOptions, this.getOptions());
+
+    /**
+     * Last animation frame
+     * @property {Number} lastAnimationFrame
+     * @type {Number}
+     */
+    this.lastAnimationFrame = null;
+    this.lastElements = [];
+
+    this[onSortableSorted] = this[onSortableSorted].bind(this);
+    this[onSortableSort] = this[onSortableSort].bind(this);
+  }
+
+  /**
+   * Attaches plugins event listeners
+   */
+  attach() {
+    this.draggable.on('sortable:sort', this[onSortableSort]);
+    this.draggable.on('sortable:sorted', this[onSortableSorted]);
+  }
+
+  /**
+   * Detaches plugins event listeners
+   */
+  detach() {
+    this.draggable.off('sortable:sort', this[onSortableSort]);
+    this.draggable.off('sortable:sorted', this[onSortableSorted]);
+  }
+
+  /**
+   * Returns options passed through draggable
+   * @return {Object}
+   */
+  getOptions() {
+    return this.draggable.options.sortAnimation || {};
+  }
+
+  /**
+   * Sortable sort handler
+   * @param {SortableSortEvent} sortableEvent
+   * @private
+   */
+  [onSortableSort]({ dragEvent }) {
+    const { sourceContainer } = dragEvent;
+    const elements = this.draggable.getDraggableElementsForContainer(sourceContainer);
+    this.lastElements = Array.from(elements).map(el => {
+      return {
+        domEl: el,
+        offsetTop: el.offsetTop,
+        offsetLeft: el.offsetLeft
+      };
+    });
+  }
+
+  /**
+   * Sortable sorted handler
+   * @param {SortableSortedEvent} sortableEvent
+   * @private
+   */
+  [onSortableSorted]({ oldIndex, newIndex }) {
+    if (oldIndex === newIndex) {
+      return;
+    }
+
+    const effectedElements = [];
+    let start;
+    let end;
+    let num;
+    if (oldIndex > newIndex) {
+      start = newIndex;
+      end = oldIndex - 1;
+      num = 1;
+    } else {
+      start = oldIndex + 1;
+      end = newIndex;
+      num = -1;
+    }
+
+    for (let i = start; i <= end; i++) {
+      const from = this.lastElements[i];
+      const to = this.lastElements[i + num];
+      effectedElements.push({ from, to });
+    }
+    cancelAnimationFrame(this.lastAnimationFrame);
+
+    // Can be done in a separate frame
+    this.lastAnimationFrame = requestAnimationFrame(() => {
+      effectedElements.forEach(element => animate(element, this.options));
+    });
+  }
+}
+
+exports.default = SortAnimation; /**
+                                  * Animates two elements
+                                  * @param {Object} element
+                                  * @param {Object} element.from
+                                  * @param {Object} element.to
+                                  * @param {Object} options
+                                  * @param {Number} options.duration
+                                  * @param {String} options.easingFunction
+                                  * @private
+                                  */
+
+function animate({ from, to }, { duration, easingFunction }) {
+  const domEl = from.domEl;
+  const x = from.offsetLeft - to.offsetLeft;
+  const y = from.offsetTop - to.offsetTop;
+
+  domEl.style.pointerEvents = 'none';
+  domEl.style.transform = `translate3d(${x}px, ${y}px, 0)`;
+
+  requestAnimationFrame(() => {
+    domEl.addEventListener('transitionend', resetElementOnTransitionEnd);
+    domEl.style.transition = `transform ${duration}ms ${easingFunction}`;
+    domEl.style.transform = '';
+  });
+}
+
+/**
+ * Resets animation style properties after animation has completed
+ * @param {Event} event
+ * @private
+ */
+function resetElementOnTransitionEnd(event) {
+  event.target.style.transition = '';
+  event.target.style.pointerEvents = '';
+  event.target.removeEventListener('transitionend', resetElementOnTransitionEnd);
+}
+
+/***/ }),
+/* 16 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _SortAnimation = __webpack_require__(15);
+
+var _SortAnimation2 = _interopRequireDefault(_SortAnimation);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _SortAnimation2.default;
+exports.defaultOptions = _SortAnimation.defaultOptions;
+
+/***/ }),
+/* 17 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onSortableSorted = Symbol('onSortableSorted');
+
+/**
+ * SwapAnimation default options
+ * @property {Object} defaultOptions
+ * @property {Number} defaultOptions.duration
+ * @property {String} defaultOptions.easingFunction
+ * @property {Boolean} defaultOptions.horizontal
+ * @type {Object}
+ */
+const defaultOptions = exports.defaultOptions = {
+  duration: 150,
+  easingFunction: 'ease-in-out',
+  horizontal: false
+};
+
+/**
+ * SwapAnimation plugin adds swap animations for sortable
+ * @class SwapAnimation
+ * @module SwapAnimation
+ * @extends AbstractPlugin
+ */
+class SwapAnimation extends _AbstractPlugin2.default {
+  /**
+   * SwapAnimation constructor.
+   * @constructs SwapAnimation
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * SwapAnimation options
+     * @property {Object} options
+     * @property {Number} defaultOptions.duration
+     * @property {String} defaultOptions.easingFunction
+     * @type {Object}
+     */
+    this.options = _extends({}, defaultOptions, this.getOptions());
+
+    /**
+     * Last animation frame
+     * @property {Number} lastAnimationFrame
+     * @type {Number}
+     */
+    this.lastAnimationFrame = null;
+
+    this[onSortableSorted] = this[onSortableSorted].bind(this);
+  }
+
+  /**
+   * Attaches plugins event listeners
+   */
+  attach() {
+    this.draggable.on('sortable:sorted', this[onSortableSorted]);
+  }
+
+  /**
+   * Detaches plugins event listeners
+   */
+  detach() {
+    this.draggable.off('sortable:sorted', this[onSortableSorted]);
+  }
+
+  /**
+   * Returns options passed through draggable
+   * @return {Object}
+   */
+  getOptions() {
+    return this.draggable.options.swapAnimation || {};
+  }
+
+  /**
+   * Sortable sorted handler
+   * @param {SortableSortedEvent} sortableEvent
+   * @private
+   */
+  [onSortableSorted]({ oldIndex, newIndex, dragEvent }) {
+    const { source, over } = dragEvent;
+
+    cancelAnimationFrame(this.lastAnimationFrame);
+
+    // Can be done in a separate frame
+    this.lastAnimationFrame = requestAnimationFrame(() => {
+      if (oldIndex >= newIndex) {
+        animate(source, over, this.options);
+      } else {
+        animate(over, source, this.options);
+      }
+    });
+  }
+}
+
+exports.default = SwapAnimation; /**
+                                  * Animates two elements
+                                  * @param {HTMLElement} from
+                                  * @param {HTMLElement} to
+                                  * @param {Object} options
+                                  * @param {Number} options.duration
+                                  * @param {String} options.easingFunction
+                                  * @param {String} options.horizontal
+                                  * @private
+                                  */
+
+function animate(from, to, { duration, easingFunction, horizontal }) {
+  for (const element of [from, to]) {
+    element.style.pointerEvents = 'none';
+  }
+
+  if (horizontal) {
+    const width = from.offsetWidth;
+    from.style.transform = `translate3d(${width}px, 0, 0)`;
+    to.style.transform = `translate3d(-${width}px, 0, 0)`;
+  } else {
+    const height = from.offsetHeight;
+    from.style.transform = `translate3d(0, ${height}px, 0)`;
+    to.style.transform = `translate3d(0, -${height}px, 0)`;
+  }
+
+  requestAnimationFrame(() => {
+    for (const element of [from, to]) {
+      element.addEventListener('transitionend', resetElementOnTransitionEnd);
+      element.style.transition = `transform ${duration}ms ${easingFunction}`;
+      element.style.transform = '';
+    }
+  });
+}
+
+/**
+ * Resets animation style properties after animation has completed
+ * @param {Event} event
+ * @private
+ */
+function resetElementOnTransitionEnd(event) {
+  event.target.style.transition = '';
+  event.target.style.pointerEvents = '';
+  event.target.removeEventListener('transitionend', resetElementOnTransitionEnd);
+}
+
+/***/ }),
+/* 18 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _SwapAnimation = __webpack_require__(17);
+
+var _SwapAnimation2 = _interopRequireDefault(_SwapAnimation);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _SwapAnimation2.default;
+exports.defaultOptions = _SwapAnimation.defaultOptions;
+
+/***/ }),
+/* 19 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+var _SnappableEvent = __webpack_require__(7);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onDragStart = Symbol('onDragStart');
+const onDragStop = Symbol('onDragStop');
+const onDragOver = Symbol('onDragOver');
+const onDragOut = Symbol('onDragOut');
+const onMirrorCreated = Symbol('onMirrorCreated');
+const onMirrorDestroy = Symbol('onMirrorDestroy');
+
+/**
+ * Snappable plugin which snaps draggable elements into place
+ * @class Snappable
+ * @module Snappable
+ * @extends AbstractPlugin
+ */
+class Snappable extends _AbstractPlugin2.default {
+  /**
+   * Snappable constructor.
+   * @constructs Snappable
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * Keeps track of the first source element
+     * @property {HTMLElement|null} firstSource
+     */
+    this.firstSource = null;
+
+    /**
+     * Keeps track of the mirror element
+     * @property {HTMLElement} mirror
+     */
+    this.mirror = null;
+
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+    this[onDragOver] = this[onDragOver].bind(this);
+    this[onDragOut] = this[onDragOut].bind(this);
+    this[onMirrorCreated] = this[onMirrorCreated].bind(this);
+    this[onMirrorDestroy] = this[onMirrorDestroy].bind(this);
+  }
+
+  /**
+   * Attaches plugins event listeners
+   */
+  attach() {
+    this.draggable.on('drag:start', this[onDragStart]).on('drag:stop', this[onDragStop]).on('drag:over', this[onDragOver]).on('drag:out', this[onDragOut]).on('droppable:over', this[onDragOver]).on('droppable:out', this[onDragOut]).on('mirror:created', this[onMirrorCreated]).on('mirror:destroy', this[onMirrorDestroy]);
+  }
+
+  /**
+   * Detaches plugins event listeners
+   */
+  detach() {
+    this.draggable.off('drag:start', this[onDragStart]).off('drag:stop', this[onDragStop]).off('drag:over', this[onDragOver]).off('drag:out', this[onDragOut]).off('droppable:over', this[onDragOver]).off('droppable:out', this[onDragOut]).off('mirror:created', this[onMirrorCreated]).off('mirror:destroy', this[onMirrorDestroy]);
+  }
+
+  /**
+   * Drag start handler
+   * @private
+   * @param {DragStartEvent} event - Drag start event
+   */
+  [onDragStart](event) {
+    if (event.canceled()) {
+      return;
+    }
+
+    this.firstSource = event.source;
+  }
+
+  /**
+   * Drag stop handler
+   * @private
+   * @param {DragStopEvent} event - Drag stop event
+   */
+  [onDragStop]() {
+    this.firstSource = null;
+  }
+
+  /**
+   * Drag over handler
+   * @private
+   * @param {DragOverEvent|DroppableOverEvent} event - Drag over event
+   */
+  [onDragOver](event) {
+    if (event.canceled()) {
+      return;
+    }
+
+    const source = event.source || event.dragEvent.source;
+
+    if (source === this.firstSource) {
+      this.firstSource = null;
+      return;
+    }
+
+    const snapInEvent = new _SnappableEvent.SnapInEvent({
+      dragEvent: event,
+      snappable: event.over || event.droppable
+    });
+
+    this.draggable.trigger(snapInEvent);
+
+    if (snapInEvent.canceled()) {
+      return;
+    }
+
+    if (this.mirror) {
+      this.mirror.style.display = 'none';
+    }
+
+    source.classList.remove(this.draggable.getClassNameFor('source:dragging'));
+    source.classList.add(this.draggable.getClassNameFor('source:placed'));
+
+    // Need to cancel this in drag out
+    setTimeout(() => {
+      source.classList.remove(this.draggable.getClassNameFor('source:placed'));
+    }, this.draggable.options.placedTimeout);
+  }
+
+  /**
+   * Drag out handler
+   * @private
+   * @param {DragOutEvent|DroppableOutEvent} event - Drag out event
+   */
+  [onDragOut](event) {
+    if (event.canceled()) {
+      return;
+    }
+
+    const source = event.source || event.dragEvent.source;
+
+    const snapOutEvent = new _SnappableEvent.SnapOutEvent({
+      dragEvent: event,
+      snappable: event.over || event.droppable
+    });
+
+    this.draggable.trigger(snapOutEvent);
+
+    if (snapOutEvent.canceled()) {
+      return;
+    }
+
+    if (this.mirror) {
+      this.mirror.style.display = '';
+    }
+
+    source.classList.add(this.draggable.getClassNameFor('source:dragging'));
+  }
+
+  /**
+   * Mirror created handler
+   * @param {MirrorCreatedEvent} mirrorEvent
+   * @private
+   */
+  [onMirrorCreated]({ mirror }) {
+    this.mirror = mirror;
+  }
+
+  /**
+   * Mirror destroy handler
+   * @param {MirrorDestroyEvent} mirrorEvent
+   * @private
+   */
+  [onMirrorDestroy]() {
+    this.mirror = null;
+  }
+}
+exports.default = Snappable;
+
+/***/ }),
+/* 20 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.SnapOutEvent = exports.SnapInEvent = exports.SnapEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base snap event
+ * @class SnapEvent
+ * @module SnapEvent
+ * @extends AbstractEvent
+ */
+class SnapEvent extends _AbstractEvent2.default {
+
+  /**
+   * Drag event that triggered this snap event
+   * @property dragEvent
+   * @type {DragEvent}
+   * @readonly
+   */
+  get dragEvent() {
+    return this.data.dragEvent;
+  }
+
+  /**
+   * Snappable element
+   * @property snappable
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get snappable() {
+    return this.data.snappable;
+  }
+}
+
+exports.SnapEvent = SnapEvent; /**
+                                * Snap in event
+                                * @class SnapInEvent
+                                * @module SnapInEvent
+                                * @extends SnapEvent
+                                */
+
+SnapEvent.type = 'snap';
+class SnapInEvent extends SnapEvent {}
+
+exports.SnapInEvent = SnapInEvent; /**
+                                    * Snap out event
+                                    * @class SnapOutEvent
+                                    * @module SnapOutEvent
+                                    * @extends SnapEvent
+                                    */
+
+SnapInEvent.type = 'snap:in';
+SnapInEvent.cancelable = true;
+class SnapOutEvent extends SnapEvent {}
+exports.SnapOutEvent = SnapOutEvent;
+SnapOutEvent.type = 'snap:out';
+SnapOutEvent.cancelable = true;
+
+/***/ }),
+/* 21 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _SnappableEvent = __webpack_require__(7);
+
+Object.keys(_SnappableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SnappableEvent[key];
+    }
+  });
+});
+
+var _Snappable = __webpack_require__(19);
+
+var _Snappable2 = _interopRequireDefault(_Snappable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Snappable2.default;
+
+/***/ }),
+/* 22 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+var _utils = __webpack_require__(2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onMirrorCreated = Symbol('onMirrorCreated');
+const onMirrorDestroy = Symbol('onMirrorDestroy');
+const onDragOver = Symbol('onDragOver');
+const resize = Symbol('resize');
+
+/**
+ * ResizeMirror default options
+ * @property {Object} defaultOptions
+ * @type {Object}
+ */
+const defaultOptions = exports.defaultOptions = {};
+
+/**
+ * The ResizeMirror plugin resizes the mirror element to the dimensions of the draggable element that the mirror is hovering over
+ * @class ResizeMirror
+ * @module ResizeMirror
+ * @extends AbstractPlugin
+ */
+class ResizeMirror extends _AbstractPlugin2.default {
+  /**
+   * ResizeMirror constructor.
+   * @constructs ResizeMirror
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * ResizeMirror options
+     * @property {Object} options
+     * @type {Object}
+     */
+    this.options = _extends({}, defaultOptions, this.getOptions());
+
+    /**
+     * ResizeMirror remembers the last width when resizing the mirror
+     * to avoid additional writes to the DOM
+     * @property {number} lastWidth
+     */
+    this.lastWidth = 0;
+
+    /**
+     * ResizeMirror remembers the last height when resizing the mirror
+     * to avoid additional writes to the DOM
+     * @property {number} lastHeight
+     */
+    this.lastHeight = 0;
+
+    /**
+     * Keeps track of the mirror element
+     * @property {HTMLElement} mirror
+     */
+    this.mirror = null;
+
+    this[onMirrorCreated] = this[onMirrorCreated].bind(this);
+    this[onMirrorDestroy] = this[onMirrorDestroy].bind(this);
+    this[onDragOver] = this[onDragOver].bind(this);
+  }
+
+  /**
+   * Attaches plugins event listeners
+   */
+  attach() {
+    this.draggable.on('mirror:created', this[onMirrorCreated]).on('drag:over', this[onDragOver]).on('drag:over:container', this[onDragOver]);
+  }
+
+  /**
+   * Detaches plugins event listeners
+   */
+  detach() {
+    this.draggable.off('mirror:created', this[onMirrorCreated]).off('mirror:destroy', this[onMirrorDestroy]).off('drag:over', this[onDragOver]).off('drag:over:container', this[onDragOver]);
+  }
+
+  /**
+   * Returns options passed through draggable
+   * @return {Object}
+   */
+  getOptions() {
+    return this.draggable.options.resizeMirror || {};
+  }
+
+  /**
+   * Mirror created handler
+   * @param {MirrorCreatedEvent} mirrorEvent
+   * @private
+   */
+  [onMirrorCreated]({ mirror }) {
+    this.mirror = mirror;
+  }
+
+  /**
+   * Mirror destroy handler
+   * @param {MirrorDestroyEvent} mirrorEvent
+   * @private
+   */
+  [onMirrorDestroy]() {
+    this.mirror = null;
+  }
+
+  /**
+   * Drag over handler
+   * @param {DragOverEvent | DragOverContainer} dragEvent
+   * @private
+   */
+  [onDragOver](dragEvent) {
+    this[resize](dragEvent);
+  }
+
+  /**
+   * Resize function for
+   * @param {DragOverEvent | DragOverContainer} dragEvent
+   * @private
+   */
+  [resize]({ overContainer, over }) {
+    requestAnimationFrame(() => {
+      if (!this.mirror.parentNode) {
+        return;
+      }
+
+      if (this.mirror.parentNode !== overContainer) {
+        overContainer.appendChild(this.mirror);
+      }
+
+      const overElement = over || this.draggable.getDraggableElementsForContainer(overContainer)[0];
+
+      if (!overElement) {
+        return;
+      }
+
+      (0, _utils.requestNextAnimationFrame)(() => {
+        const overRect = overElement.getBoundingClientRect();
+
+        if (this.lastHeight === overRect.height && this.lastWidth === overRect.width) {
+          return;
+        }
+
+        this.mirror.style.width = `${overRect.width}px`;
+        this.mirror.style.height = `${overRect.height}px`;
+
+        this.lastWidth = overRect.width;
+        this.lastHeight = overRect.height;
+      });
+    });
+  }
+}
+exports.default = ResizeMirror;
+
+/***/ }),
+/* 23 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _ResizeMirror = __webpack_require__(22);
+
+var _ResizeMirror2 = _interopRequireDefault(_ResizeMirror);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _ResizeMirror2.default;
+exports.defaultOptions = _ResizeMirror.defaultOptions;
+
+/***/ }),
+/* 24 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+var _utils = __webpack_require__(2);
+
+var _CollidableEvent = __webpack_require__(8);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onDragMove = Symbol('onDragMove');
+const onDragStop = Symbol('onDragStop');
+const onRequestAnimationFrame = Symbol('onRequestAnimationFrame');
+
+/**
+ * Collidable plugin which detects colliding elements while dragging
+ * @class Collidable
+ * @module Collidable
+ * @extends AbstractPlugin
+ */
+class Collidable extends _AbstractPlugin2.default {
+  /**
+   * Collidable constructor.
+   * @constructs Collidable
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * Keeps track of currently colliding elements
+     * @property {HTMLElement|null} currentlyCollidingElement
+     * @type {HTMLElement|null}
+     */
+    this.currentlyCollidingElement = null;
+
+    /**
+     * Keeps track of currently colliding elements
+     * @property {HTMLElement|null} lastCollidingElement
+     * @type {HTMLElement|null}
+     */
+    this.lastCollidingElement = null;
+
+    /**
+     * Animation frame for finding colliding elements
+     * @property {Number|null} currentAnimationFrame
+     * @type {Number|null}
+     */
+    this.currentAnimationFrame = null;
+
+    this[onDragMove] = this[onDragMove].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+    this[onRequestAnimationFrame] = this[onRequestAnimationFrame].bind(this);
+  }
+
+  /**
+   * Attaches plugins event listeners
+   */
+  attach() {
+    this.draggable.on('drag:move', this[onDragMove]).on('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Detaches plugins event listeners
+   */
+  detach() {
+    this.draggable.off('drag:move', this[onDragMove]).off('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Returns current collidables based on `collidables` option
+   * @return {HTMLElement[]}
+   */
+  getCollidables() {
+    const collidables = this.draggable.options.collidables;
+
+    if (typeof collidables === 'string') {
+      return Array.prototype.slice.call(document.querySelectorAll(collidables));
+    } else if (collidables instanceof NodeList || collidables instanceof Array) {
+      return Array.prototype.slice.call(collidables);
+    } else if (collidables instanceof HTMLElement) {
+      return [collidables];
+    } else if (typeof collidables === 'function') {
+      return collidables();
+    } else {
+      return [];
+    }
+  }
+
+  /**
+   * Drag move handler
+   * @private
+   * @param {DragMoveEvent} event - Drag move event
+   */
+  [onDragMove](event) {
+    const target = event.sensorEvent.target;
+
+    this.currentAnimationFrame = requestAnimationFrame(this[onRequestAnimationFrame](target));
+
+    if (this.currentlyCollidingElement) {
+      event.cancel();
+    }
+
+    const collidableInEvent = new _CollidableEvent.CollidableInEvent({
+      dragEvent: event,
+      collidingElement: this.currentlyCollidingElement
+    });
+
+    const collidableOutEvent = new _CollidableEvent.CollidableOutEvent({
+      dragEvent: event,
+      collidingElement: this.lastCollidingElement
+    });
+
+    const enteringCollidable = Boolean(this.currentlyCollidingElement && this.lastCollidingElement !== this.currentlyCollidingElement);
+    const leavingCollidable = Boolean(!this.currentlyCollidingElement && this.lastCollidingElement);
+
+    if (enteringCollidable) {
+      if (this.lastCollidingElement) {
+        this.draggable.trigger(collidableOutEvent);
+      }
+
+      this.draggable.trigger(collidableInEvent);
+    } else if (leavingCollidable) {
+      this.draggable.trigger(collidableOutEvent);
+    }
+
+    this.lastCollidingElement = this.currentlyCollidingElement;
+  }
+
+  /**
+   * Drag stop handler
+   * @private
+   * @param {DragStopEvent} event - Drag stop event
+   */
+  [onDragStop](event) {
+    const lastCollidingElement = this.currentlyCollidingElement || this.lastCollidingElement;
+    const collidableOutEvent = new _CollidableEvent.CollidableOutEvent({
+      dragEvent: event,
+      collidingElement: lastCollidingElement
+    });
+
+    if (lastCollidingElement) {
+      this.draggable.trigger(collidableOutEvent);
+    }
+
+    this.lastCollidingElement = null;
+    this.currentlyCollidingElement = null;
+  }
+
+  /**
+   * Animation frame function
+   * @private
+   * @param {HTMLElement} target - Current move target
+   * @return {Function}
+   */
+  [onRequestAnimationFrame](target) {
+    return () => {
+      const collidables = this.getCollidables();
+      this.currentlyCollidingElement = (0, _utils.closest)(target, element => collidables.includes(element));
+    };
+  }
+}
+exports.default = Collidable;
+
+/***/ }),
+/* 25 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.CollidableOutEvent = exports.CollidableInEvent = exports.CollidableEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base collidable event
+ * @class CollidableEvent
+ * @module CollidableEvent
+ * @extends AbstractEvent
+ */
+class CollidableEvent extends _AbstractEvent2.default {
+
+  /**
+   * Drag event that triggered this colliable event
+   * @property dragEvent
+   * @type {DragEvent}
+   * @readonly
+   */
+  get dragEvent() {
+    return this.data.dragEvent;
+  }
+}
+
+exports.CollidableEvent = CollidableEvent; /**
+                                            * Collidable in event
+                                            * @class CollidableInEvent
+                                            * @module CollidableInEvent
+                                            * @extends CollidableEvent
+                                            */
+
+CollidableEvent.type = 'collidable';
+class CollidableInEvent extends CollidableEvent {
+
+  /**
+   * Element you are currently colliding with
+   * @property collidingElement
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get collidingElement() {
+    return this.data.collidingElement;
+  }
+}
+
+exports.CollidableInEvent = CollidableInEvent; /**
+                                                * Collidable out event
+                                                * @class CollidableOutEvent
+                                                * @module CollidableOutEvent
+                                                * @extends CollidableEvent
+                                                */
+
+CollidableInEvent.type = 'collidable:in';
+class CollidableOutEvent extends CollidableEvent {
+
+  /**
+   * Element you were previously colliding with
+   * @property collidingElement
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get collidingElement() {
+    return this.data.collidingElement;
+  }
+}
+exports.CollidableOutEvent = CollidableOutEvent;
+CollidableOutEvent.type = 'collidable:out';
+
+/***/ }),
+/* 26 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _CollidableEvent = __webpack_require__(8);
+
+Object.keys(_CollidableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _CollidableEvent[key];
+    }
+  });
+});
+
+var _Collidable = __webpack_require__(24);
+
+var _Collidable2 = _interopRequireDefault(_Collidable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Collidable2.default;
+
+/***/ }),
+/* 27 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _Collidable = __webpack_require__(26);
+
+Object.defineProperty(exports, 'Collidable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Collidable).default;
+  }
+});
+
+var _ResizeMirror = __webpack_require__(23);
+
+Object.defineProperty(exports, 'ResizeMirror', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_ResizeMirror).default;
+  }
+});
+Object.defineProperty(exports, 'defaultResizeMirrorOptions', {
+  enumerable: true,
+  get: function () {
+    return _ResizeMirror.defaultOptions;
+  }
+});
+
+var _Snappable = __webpack_require__(21);
+
+Object.defineProperty(exports, 'Snappable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Snappable).default;
+  }
+});
+
+var _SwapAnimation = __webpack_require__(18);
+
+Object.defineProperty(exports, 'SwapAnimation', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_SwapAnimation).default;
+  }
+});
+Object.defineProperty(exports, 'defaultSwapAnimationOptions', {
+  enumerable: true,
+  get: function () {
+    return _SwapAnimation.defaultOptions;
+  }
+});
+
+var _SortAnimation = __webpack_require__(16);
+
+Object.defineProperty(exports, 'SortAnimation', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_SortAnimation).default;
+  }
+});
+Object.defineProperty(exports, 'defaultSortAnimationOptions', {
+  enumerable: true,
+  get: function () {
+    return _SortAnimation.defaultOptions;
+  }
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/***/ }),
+/* 28 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _Draggable = __webpack_require__(5);
+
+var _Draggable2 = _interopRequireDefault(_Draggable);
+
+var _SortableEvent = __webpack_require__(9);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onDragStart = Symbol('onDragStart');
+const onDragOverContainer = Symbol('onDragOverContainer');
+const onDragOver = Symbol('onDragOver');
+const onDragStop = Symbol('onDragStop');
+
+/**
+ * Returns announcement message when a Draggable element has been sorted with another Draggable element
+ * or moved into a new container
+ * @param {SortableSortedEvent} sortableEvent
+ * @return {String}
+ */
+function onSortableSortedDefaultAnnouncement({ dragEvent }) {
+  const sourceText = dragEvent.source.textContent.trim() || dragEvent.source.id || 'sortable element';
+
+  if (dragEvent.over) {
+    const overText = dragEvent.over.textContent.trim() || dragEvent.over.id || 'sortable element';
+    const isFollowing = dragEvent.source.compareDocumentPosition(dragEvent.over) & Node.DOCUMENT_POSITION_FOLLOWING;
+
+    if (isFollowing) {
+      return `Placed ${sourceText} after ${overText}`;
+    } else {
+      return `Placed ${sourceText} before ${overText}`;
+    }
+  } else {
+    // need to figure out how to compute container name
+    return `Placed ${sourceText} into a different container`;
+  }
+}
+
+/**
+ * @const {Object} defaultAnnouncements
+ * @const {Function} defaultAnnouncements['sortable:sorted']
+ */
+const defaultAnnouncements = {
+  'sortable:sorted': onSortableSortedDefaultAnnouncement
+};
+
+/**
+ * Sortable is built on top of Draggable and allows sorting of draggable elements. Sortable will keep
+ * track of the original index and emits the new index as you drag over draggable elements.
+ * @class Sortable
+ * @module Sortable
+ * @extends Draggable
+ */
+class Sortable extends _Draggable2.default {
+  /**
+   * Sortable constructor.
+   * @constructs Sortable
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Sortable containers
+   * @param {Object} options - Options for Sortable
+   */
+  constructor(containers = [], options = {}) {
+    super(containers, _extends({}, options, {
+      announcements: _extends({}, defaultAnnouncements, options.announcements || {})
+    }));
+
+    /**
+     * start index of source on drag start
+     * @property startIndex
+     * @type {Number}
+     */
+    this.startIndex = null;
+
+    /**
+     * start container on drag start
+     * @property startContainer
+     * @type {HTMLElement}
+     * @default null
+     */
+    this.startContainer = null;
+
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragOverContainer] = this[onDragOverContainer].bind(this);
+    this[onDragOver] = this[onDragOver].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+
+    this.on('drag:start', this[onDragStart]).on('drag:over:container', this[onDragOverContainer]).on('drag:over', this[onDragOver]).on('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Destroys Sortable instance.
+   */
+  destroy() {
+    super.destroy();
+
+    this.off('drag:start', this[onDragStart]).off('drag:over:container', this[onDragOverContainer]).off('drag:over', this[onDragOver]).off('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Returns true index of element within its container during drag operation, i.e. excluding mirror and original source
+   * @param {HTMLElement} element - An element
+   * @return {Number}
+   */
+  index(element) {
+    return this.getDraggableElementsForContainer(element.parentNode).indexOf(element);
+  }
+
+  /**
+   * Drag start handler
+   * @private
+   * @param {DragStartEvent} event - Drag start event
+   */
+  [onDragStart](event) {
+    this.startContainer = event.source.parentNode;
+    this.startIndex = this.index(event.source);
+
+    const sortableStartEvent = new _SortableEvent.SortableStartEvent({
+      dragEvent: event,
+      startIndex: this.startIndex,
+      startContainer: this.startContainer
+    });
+
+    this.trigger(sortableStartEvent);
+
+    if (sortableStartEvent.canceled()) {
+      event.cancel();
+    }
+  }
+
+  /**
+   * Drag over container handler
+   * @private
+   * @param {DragOverContainerEvent} event - Drag over container event
+   */
+  [onDragOverContainer](event) {
+    if (event.canceled()) {
+      return;
+    }
+
+    const { source, over, overContainer } = event;
+    const oldIndex = this.index(source);
+
+    const sortableSortEvent = new _SortableEvent.SortableSortEvent({
+      dragEvent: event,
+      currentIndex: oldIndex,
+      source,
+      over
+    });
+
+    this.trigger(sortableSortEvent);
+
+    if (sortableSortEvent.canceled()) {
+      return;
+    }
+
+    const children = this.getDraggableElementsForContainer(overContainer);
+    const moves = move({ source, over, overContainer, children });
+
+    if (!moves) {
+      return;
+    }
+
+    const { oldContainer, newContainer } = moves;
+    const newIndex = this.index(event.source);
+
+    const sortableSortedEvent = new _SortableEvent.SortableSortedEvent({
+      dragEvent: event,
+      oldIndex,
+      newIndex,
+      oldContainer,
+      newContainer
+    });
+
+    this.trigger(sortableSortedEvent);
+  }
+
+  /**
+   * Drag over handler
+   * @private
+   * @param {DragOverEvent} event - Drag over event
+   */
+  [onDragOver](event) {
+    if (event.over === event.originalSource || event.over === event.source) {
+      return;
+    }
+
+    const { source, over, overContainer } = event;
+    const oldIndex = this.index(source);
+
+    const sortableSortEvent = new _SortableEvent.SortableSortEvent({
+      dragEvent: event,
+      currentIndex: oldIndex,
+      source,
+      over
+    });
+
+    this.trigger(sortableSortEvent);
+
+    if (sortableSortEvent.canceled()) {
+      return;
+    }
+
+    const children = this.getDraggableElementsForContainer(overContainer);
+    const moves = move({ source, over, overContainer, children });
+
+    if (!moves) {
+      return;
+    }
+
+    const { oldContainer, newContainer } = moves;
+    const newIndex = this.index(source);
+
+    const sortableSortedEvent = new _SortableEvent.SortableSortedEvent({
+      dragEvent: event,
+      oldIndex,
+      newIndex,
+      oldContainer,
+      newContainer
+    });
+
+    this.trigger(sortableSortedEvent);
+  }
+
+  /**
+   * Drag stop handler
+   * @private
+   * @param {DragStopEvent} event - Drag stop event
+   */
+  [onDragStop](event) {
+    const sortableStopEvent = new _SortableEvent.SortableStopEvent({
+      dragEvent: event,
+      oldIndex: this.startIndex,
+      newIndex: this.index(event.source),
+      oldContainer: this.startContainer,
+      newContainer: event.source.parentNode
+    });
+
+    this.trigger(sortableStopEvent);
+
+    this.startIndex = null;
+    this.startContainer = null;
+  }
+}
+
+exports.default = Sortable;
+function index(element) {
+  return Array.prototype.indexOf.call(element.parentNode.children, element);
+}
+
+function move({ source, over, overContainer, children }) {
+  const emptyOverContainer = !children.length;
+  const differentContainer = source.parentNode !== overContainer;
+  const sameContainer = over && !differentContainer;
+
+  if (emptyOverContainer) {
+    return moveInsideEmptyContainer(source, overContainer);
+  } else if (sameContainer) {
+    return moveWithinContainer(source, over);
+  } else if (differentContainer) {
+    return moveOutsideContainer(source, over, overContainer);
+  } else {
+    return null;
+  }
+}
+
+function moveInsideEmptyContainer(source, overContainer) {
+  const oldContainer = source.parentNode;
+
+  overContainer.appendChild(source);
+
+  return { oldContainer, newContainer: overContainer };
+}
+
+function moveWithinContainer(source, over) {
+  const oldIndex = index(source);
+  const newIndex = index(over);
+
+  if (oldIndex < newIndex) {
+    source.parentNode.insertBefore(source, over.nextElementSibling);
+  } else {
+    source.parentNode.insertBefore(source, over);
+  }
+
+  return { oldContainer: source.parentNode, newContainer: source.parentNode };
+}
+
+function moveOutsideContainer(source, over, overContainer) {
+  const oldContainer = source.parentNode;
+
+  if (over) {
+    over.parentNode.insertBefore(source, over);
+  } else {
+    // need to figure out proper position
+    overContainer.appendChild(source);
+  }
+
+  return { oldContainer, newContainer: source.parentNode };
+}
+
+/***/ }),
+/* 29 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.SortableStopEvent = exports.SortableSortedEvent = exports.SortableSortEvent = exports.SortableStartEvent = exports.SortableEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base sortable event
+ * @class SortableEvent
+ * @module SortableEvent
+ * @extends AbstractEvent
+ */
+class SortableEvent extends _AbstractEvent2.default {
+
+  /**
+   * Original drag event that triggered this sortable event
+   * @property dragEvent
+   * @type {DragEvent}
+   * @readonly
+   */
+  get dragEvent() {
+    return this.data.dragEvent;
+  }
+}
+
+exports.SortableEvent = SortableEvent; /**
+                                        * Sortable start event
+                                        * @class SortableStartEvent
+                                        * @module SortableStartEvent
+                                        * @extends SortableEvent
+                                        */
+
+SortableEvent.type = 'sortable';
+class SortableStartEvent extends SortableEvent {
+
+  /**
+   * Start index of source on sortable start
+   * @property startIndex
+   * @type {Number}
+   * @readonly
+   */
+  get startIndex() {
+    return this.data.startIndex;
+  }
+
+  /**
+   * Start container on sortable start
+   * @property startContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get startContainer() {
+    return this.data.startContainer;
+  }
+}
+
+exports.SortableStartEvent = SortableStartEvent; /**
+                                                  * Sortable sort event
+                                                  * @class SortableSortEvent
+                                                  * @module SortableSortEvent
+                                                  * @extends SortableEvent
+                                                  */
+
+SortableStartEvent.type = 'sortable:start';
+SortableStartEvent.cancelable = true;
+class SortableSortEvent extends SortableEvent {
+
+  /**
+   * Index of current draggable element
+   * @property currentIndex
+   * @type {Number}
+   * @readonly
+   */
+  get currentIndex() {
+    return this.data.currentIndex;
+  }
+
+  /**
+   * Draggable element you are hovering over
+   * @property over
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get over() {
+    return this.data.over;
+  }
+
+  /**
+   * Draggable container element you are hovering over
+   * @property overContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get overContainer() {
+    return this.data.dragEvent.overContainer;
+  }
+}
+
+exports.SortableSortEvent = SortableSortEvent; /**
+                                                * Sortable sorted event
+                                                * @class SortableSortedEvent
+                                                * @module SortableSortedEvent
+                                                * @extends SortableEvent
+                                                */
+
+SortableSortEvent.type = 'sortable:sort';
+SortableSortEvent.cancelable = true;
+class SortableSortedEvent extends SortableEvent {
+
+  /**
+   * Index of last sorted event
+   * @property oldIndex
+   * @type {Number}
+   * @readonly
+   */
+  get oldIndex() {
+    return this.data.oldIndex;
+  }
+
+  /**
+   * New index of this sorted event
+   * @property newIndex
+   * @type {Number}
+   * @readonly
+   */
+  get newIndex() {
+    return this.data.newIndex;
+  }
+
+  /**
+   * Old container of draggable element
+   * @property oldContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get oldContainer() {
+    return this.data.oldContainer;
+  }
+
+  /**
+   * New container of draggable element
+   * @property newContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get newContainer() {
+    return this.data.newContainer;
+  }
+}
+
+exports.SortableSortedEvent = SortableSortedEvent; /**
+                                                    * Sortable stop event
+                                                    * @class SortableStopEvent
+                                                    * @module SortableStopEvent
+                                                    * @extends SortableEvent
+                                                    */
+
+SortableSortedEvent.type = 'sortable:sorted';
+class SortableStopEvent extends SortableEvent {
+
+  /**
+   * Original index on sortable start
+   * @property oldIndex
+   * @type {Number}
+   * @readonly
+   */
+  get oldIndex() {
+    return this.data.oldIndex;
+  }
+
+  /**
+   * New index of draggable element
+   * @property newIndex
+   * @type {Number}
+   * @readonly
+   */
+  get newIndex() {
+    return this.data.newIndex;
+  }
+
+  /**
+   * Original container of draggable element
+   * @property oldContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get oldContainer() {
+    return this.data.oldContainer;
+  }
+
+  /**
+   * New container of draggable element
+   * @property newContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get newContainer() {
+    return this.data.newContainer;
+  }
+}
+exports.SortableStopEvent = SortableStopEvent;
+SortableStopEvent.type = 'sortable:stop';
+
+/***/ }),
+/* 30 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _SortableEvent = __webpack_require__(9);
+
+Object.keys(_SortableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SortableEvent[key];
+    }
+  });
+});
+
+var _Sortable = __webpack_require__(28);
+
+var _Sortable2 = _interopRequireDefault(_Sortable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Sortable2.default;
+
+/***/ }),
+/* 31 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _Draggable = __webpack_require__(5);
+
+var _Draggable2 = _interopRequireDefault(_Draggable);
+
+var _SwappableEvent = __webpack_require__(10);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onDragStart = Symbol('onDragStart');
+const onDragOver = Symbol('onDragOver');
+const onDragStop = Symbol('onDragStop');
+
+/**
+ * Returns an announcement message when the Draggable element is swapped with another draggable element
+ * @param {SwappableSwappedEvent} swappableEvent
+ * @return {String}
+ */
+function onSwappableSwappedDefaultAnnouncement({ dragEvent, swappedElement }) {
+  const sourceText = dragEvent.source.textContent.trim() || dragEvent.source.id || 'swappable element';
+  const overText = swappedElement.textContent.trim() || swappedElement.id || 'swappable element';
+
+  return `Swapped ${sourceText} with ${overText}`;
+}
+
+/**
+ * @const {Object} defaultAnnouncements
+ * @const {Function} defaultAnnouncements['swappabled:swapped']
+ */
+const defaultAnnouncements = {
+  'swappabled:swapped': onSwappableSwappedDefaultAnnouncement
+};
+
+/**
+ * Swappable is built on top of Draggable and allows swapping of draggable elements.
+ * Order is irrelevant to Swappable.
+ * @class Swappable
+ * @module Swappable
+ * @extends Draggable
+ */
+class Swappable extends _Draggable2.default {
+  /**
+   * Swappable constructor.
+   * @constructs Swappable
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Swappable containers
+   * @param {Object} options - Options for Swappable
+   */
+  constructor(containers = [], options = {}) {
+    super(containers, _extends({}, options, {
+      announcements: _extends({}, defaultAnnouncements, options.announcements || {})
+    }));
+
+    /**
+     * Last draggable element that was dragged over
+     * @property lastOver
+     * @type {HTMLElement}
+     */
+    this.lastOver = null;
+
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragOver] = this[onDragOver].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+
+    this.on('drag:start', this[onDragStart]).on('drag:over', this[onDragOver]).on('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Destroys Swappable instance.
+   */
+  destroy() {
+    super.destroy();
+
+    this.off('drag:start', this._onDragStart).off('drag:over', this._onDragOver).off('drag:stop', this._onDragStop);
+  }
+
+  /**
+   * Drag start handler
+   * @private
+   * @param {DragStartEvent} event - Drag start event
+   */
+  [onDragStart](event) {
+    const swappableStartEvent = new _SwappableEvent.SwappableStartEvent({
+      dragEvent: event
+    });
+
+    this.trigger(swappableStartEvent);
+
+    if (swappableStartEvent.canceled()) {
+      event.cancel();
+    }
+  }
+
+  /**
+   * Drag over handler
+   * @private
+   * @param {DragOverEvent} event - Drag over event
+   */
+  [onDragOver](event) {
+    if (event.over === event.originalSource || event.over === event.source || event.canceled()) {
+      return;
+    }
+
+    const swappableSwapEvent = new _SwappableEvent.SwappableSwapEvent({
+      dragEvent: event,
+      over: event.over,
+      overContainer: event.overContainer
+    });
+
+    this.trigger(swappableSwapEvent);
+
+    if (swappableSwapEvent.canceled()) {
+      return;
+    }
+
+    // swap originally swapped element back
+    if (this.lastOver && this.lastOver !== event.over) {
+      swap(this.lastOver, event.source);
+    }
+
+    if (this.lastOver === event.over) {
+      this.lastOver = null;
+    } else {
+      this.lastOver = event.over;
+    }
+
+    swap(event.source, event.over);
+
+    const swappableSwappedEvent = new _SwappableEvent.SwappableSwappedEvent({
+      dragEvent: event,
+      swappedElement: event.over
+    });
+
+    this.trigger(swappableSwappedEvent);
+  }
+
+  /**
+   * Drag stop handler
+   * @private
+   * @param {DragStopEvent} event - Drag stop event
+   */
+  [onDragStop](event) {
+    const swappableStopEvent = new _SwappableEvent.SwappableStopEvent({
+      dragEvent: event
+    });
+
+    this.trigger(swappableStopEvent);
+    this.lastOver = null;
+  }
+}
+
+exports.default = Swappable;
+function withTempElement(callback) {
+  const tmpElement = document.createElement('div');
+  callback(tmpElement);
+  tmpElement.parentNode.removeChild(tmpElement);
+}
+
+function swap(source, over) {
+  const overParent = over.parentNode;
+  const sourceParent = source.parentNode;
+
+  withTempElement(tmpElement => {
+    sourceParent.insertBefore(tmpElement, source);
+    overParent.insertBefore(source, over);
+    sourceParent.insertBefore(over, tmpElement);
+  });
+}
+
+/***/ }),
+/* 32 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.SwappableStopEvent = exports.SwappableSwappedEvent = exports.SwappableSwapEvent = exports.SwappableStartEvent = exports.SwappableEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base swappable event
+ * @class SwappableEvent
+ * @module SwappableEvent
+ * @extends AbstractEvent
+ */
+class SwappableEvent extends _AbstractEvent2.default {
+
+  /**
+   * Original drag event that triggered this swappable event
+   * @property dragEvent
+   * @type {DragEvent}
+   * @readonly
+   */
+  get dragEvent() {
+    return this.data.dragEvent;
+  }
+}
+
+exports.SwappableEvent = SwappableEvent; /**
+                                          * Swappable start event
+                                          * @class SwappableStartEvent
+                                          * @module SwappableStartEvent
+                                          * @extends SwappableEvent
+                                          */
+
+SwappableEvent.type = 'swappable';
+class SwappableStartEvent extends SwappableEvent {}
+
+exports.SwappableStartEvent = SwappableStartEvent; /**
+                                                    * Swappable swap event
+                                                    * @class SwappableSwapEvent
+                                                    * @module SwappableSwapEvent
+                                                    * @extends SwappableEvent
+                                                    */
+
+SwappableStartEvent.type = 'swappable:start';
+SwappableStartEvent.cancelable = true;
+class SwappableSwapEvent extends SwappableEvent {
+
+  /**
+   * Draggable element you are over
+   * @property over
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get over() {
+    return this.data.over;
+  }
+
+  /**
+   * Draggable container you are over
+   * @property overContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get overContainer() {
+    return this.data.overContainer;
+  }
+}
+
+exports.SwappableSwapEvent = SwappableSwapEvent; /**
+                                                  * Swappable swapped event
+                                                  * @class SwappableSwappedEvent
+                                                  * @module SwappableSwappedEvent
+                                                  * @extends SwappableEvent
+                                                  */
+
+SwappableSwapEvent.type = 'swappable:swap';
+SwappableSwapEvent.cancelable = true;
+class SwappableSwappedEvent extends SwappableEvent {
+
+  /**
+   * The draggable element that you swapped with
+   * @property swappedElement
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get swappedElement() {
+    return this.data.swappedElement;
+  }
+}
+
+exports.SwappableSwappedEvent = SwappableSwappedEvent; /**
+                                                        * Swappable stop event
+                                                        * @class SwappableStopEvent
+                                                        * @module SwappableStopEvent
+                                                        * @extends SwappableEvent
+                                                        */
+
+SwappableSwappedEvent.type = 'swappable:swapped';
+class SwappableStopEvent extends SwappableEvent {}
+exports.SwappableStopEvent = SwappableStopEvent;
+SwappableStopEvent.type = 'swappable:stop';
+
+/***/ }),
+/* 33 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _SwappableEvent = __webpack_require__(10);
+
+Object.keys(_SwappableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _SwappableEvent[key];
+    }
+  });
+});
+
+var _Swappable = __webpack_require__(31);
+
+var _Swappable2 = _interopRequireDefault(_Swappable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Swappable2.default;
+
+/***/ }),
+/* 34 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _utils = __webpack_require__(2);
+
+var _Draggable = __webpack_require__(5);
+
+var _Draggable2 = _interopRequireDefault(_Draggable);
+
+var _DroppableEvent = __webpack_require__(11);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onDragStart = Symbol('onDragStart');
+const onDragMove = Symbol('onDragMove');
+const onDragStop = Symbol('onDragStop');
+const dropInDropzone = Symbol('dropInDropZone');
+const returnToOriginalDropzone = Symbol('returnToOriginalDropzone');
+const closestDropzone = Symbol('closestDropzone');
+const getDropzones = Symbol('getDropzones');
+
+/**
+ * Returns an announcement message when the Draggable element is dropped into a dropzone element
+ * @param {DroppableDroppedEvent} droppableEvent
+ * @return {String}
+ */
+function onDroppableDroppedDefaultAnnouncement({ dragEvent, dropzone }) {
+  const sourceText = dragEvent.source.textContent.trim() || dragEvent.source.id || 'draggable element';
+  const dropzoneText = dropzone.textContent.trim() || dropzone.id || 'droppable element';
+
+  return `Dropped ${sourceText} into ${dropzoneText}`;
+}
+
+/**
+ * Returns an announcement message when the Draggable element has returned to its original dropzone element
+ * @param {DroppableReturnedEvent} droppableEvent
+ * @return {String}
+ */
+function onDroppableReturnedDefaultAnnouncement({ dragEvent, dropzone }) {
+  const sourceText = dragEvent.source.textContent.trim() || dragEvent.source.id || 'draggable element';
+  const dropzoneText = dropzone.textContent.trim() || dropzone.id || 'droppable element';
+
+  return `Returned ${sourceText} from ${dropzoneText}`;
+}
+
+/**
+ * @const {Object} defaultAnnouncements
+ * @const {Function} defaultAnnouncements['droppable:dropped']
+ * @const {Function} defaultAnnouncements['droppable:returned']
+ */
+const defaultAnnouncements = {
+  'droppable:dropped': onDroppableDroppedDefaultAnnouncement,
+  'droppable:returned': onDroppableReturnedDefaultAnnouncement
+};
+
+const defaultClasses = {
+  'droppable:active': 'draggable-dropzone--active',
+  'droppable:occupied': 'draggable-dropzone--occupied'
+};
+
+const defaultOptions = {
+  dropzone: '.draggable-droppable'
+};
+
+/**
+ * Droppable is built on top of Draggable and allows dropping draggable elements
+ * into dropzone element
+ * @class Droppable
+ * @module Droppable
+ * @extends Draggable
+ */
+class Droppable extends _Draggable2.default {
+  /**
+   * Droppable constructor.
+   * @constructs Droppable
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Droppable containers
+   * @param {Object} options - Options for Droppable
+   */
+  constructor(containers = [], options = {}) {
+    super(containers, _extends({}, defaultOptions, options, {
+      classes: _extends({}, defaultClasses, options.classes || {}),
+      announcements: _extends({}, defaultAnnouncements, options.announcements || {})
+    }));
+
+    /**
+     * All dropzone elements on drag start
+     * @property dropzones
+     * @type {HTMLElement[]}
+     */
+    this.dropzones = null;
+
+    /**
+     * Last dropzone element that the source was dropped into
+     * @property lastDropzone
+     * @type {HTMLElement}
+     */
+    this.lastDropzone = null;
+
+    /**
+     * Initial dropzone element that the source was drag from
+     * @property initialDropzone
+     * @type {HTMLElement}
+     */
+    this.initialDropzone = null;
+
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragMove] = this[onDragMove].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+
+    this.on('drag:start', this[onDragStart]).on('drag:move', this[onDragMove]).on('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Destroys Droppable instance.
+   */
+  destroy() {
+    super.destroy();
+
+    this.off('drag:start', this[onDragStart]).off('drag:move', this[onDragMove]).off('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Drag start handler
+   * @private
+   * @param {DragStartEvent} event - Drag start event
+   */
+  [onDragStart](event) {
+    if (event.canceled()) {
+      return;
+    }
+
+    this.dropzones = [...this[getDropzones]()];
+    const dropzone = (0, _utils.closest)(event.sensorEvent.target, this.options.dropzone);
+
+    if (!dropzone) {
+      event.cancel();
+      return;
+    }
+
+    const droppableStartEvent = new _DroppableEvent.DroppableStartEvent({
+      dragEvent: event,
+      dropzone
+    });
+
+    this.trigger(droppableStartEvent);
+
+    if (droppableStartEvent.canceled()) {
+      event.cancel();
+      return;
+    }
+
+    this.initialDropzone = dropzone;
+
+    for (const dropzoneElement of this.dropzones) {
+      if (dropzoneElement.classList.contains(this.getClassNameFor('droppable:occupied'))) {
+        continue;
+      }
+
+      dropzoneElement.classList.add(this.getClassNameFor('droppable:active'));
+    }
+  }
+
+  /**
+   * Drag move handler
+   * @private
+   * @param {DragMoveEvent} event - Drag move event
+   */
+  [onDragMove](event) {
+    if (event.canceled()) {
+      return;
+    }
+
+    const dropzone = this[closestDropzone](event.sensorEvent.target);
+    const overEmptyDropzone = dropzone && !dropzone.classList.contains(this.getClassNameFor('droppable:occupied'));
+
+    if (overEmptyDropzone && this[dropInDropzone](event, dropzone)) {
+      this.lastDropzone = dropzone;
+    } else if ((!dropzone || dropzone === this.initialDropzone) && this.lastDropzone) {
+      this[returnToOriginalDropzone](event);
+      this.lastDropzone = null;
+    }
+  }
+
+  /**
+   * Drag stop handler
+   * @private
+   * @param {DragStopEvent} event - Drag stop event
+   */
+  [onDragStop](event) {
+    const droppableStopEvent = new _DroppableEvent.DroppableStopEvent({
+      dragEvent: event,
+      dropzone: this.lastDropzone || this.initialDropzone
+    });
+
+    this.trigger(droppableStopEvent);
+
+    const occupiedClass = this.getClassNameFor('droppable:occupied');
+
+    for (const dropzone of this.dropzones) {
+      dropzone.classList.remove(this.getClassNameFor('droppable:active'));
+    }
+
+    if (this.lastDropzone && this.lastDropzone !== this.initialDropzone) {
+      this.initialDropzone.classList.remove(occupiedClass);
+    }
+
+    this.dropzones = null;
+    this.lastDropzone = null;
+    this.initialDropzone = null;
+  }
+
+  /**
+   * Drops a draggable element into a dropzone element
+   * @private
+   * @param {DragMoveEvent} event - Drag move event
+   * @param {HTMLElement} dropzone - Dropzone element to drop draggable into
+   */
+  [dropInDropzone](event, dropzone) {
+    const droppableDroppedEvent = new _DroppableEvent.DroppableDroppedEvent({
+      dragEvent: event,
+      dropzone
+    });
+
+    this.trigger(droppableDroppedEvent);
+
+    if (droppableDroppedEvent.canceled()) {
+      return false;
+    }
+
+    const occupiedClass = this.getClassNameFor('droppable:occupied');
+
+    if (this.lastDropzone) {
+      this.lastDropzone.classList.remove(occupiedClass);
+    }
+
+    dropzone.appendChild(event.source);
+    dropzone.classList.add(occupiedClass);
+
+    return true;
+  }
+
+  /**
+   * Moves the previously dropped element back into its original dropzone
+   * @private
+   * @param {DragMoveEvent} event - Drag move event
+   */
+  [returnToOriginalDropzone](event) {
+    const droppableReturnedEvent = new _DroppableEvent.DroppableReturnedEvent({
+      dragEvent: event,
+      dropzone: this.lastDropzone
+    });
+
+    this.trigger(droppableReturnedEvent);
+
+    if (droppableReturnedEvent.canceled()) {
+      return;
+    }
+
+    this.initialDropzone.appendChild(event.source);
+    this.lastDropzone.classList.remove(this.getClassNameFor('droppable:occupied'));
+  }
+
+  /**
+   * Returns closest dropzone element for even target
+   * @private
+   * @param {HTMLElement} target - Event target
+   * @return {HTMLElement|null}
+   */
+  [closestDropzone](target) {
+    if (!this.dropzones) {
+      return null;
+    }
+
+    return (0, _utils.closest)(target, this.dropzones);
+  }
+
+  /**
+   * Returns all current dropzone elements for this draggable instance
+   * @private
+   * @return {NodeList|HTMLElement[]|Array}
+   */
+  [getDropzones]() {
+    const dropzone = this.options.dropzone;
+
+    if (typeof dropzone === 'string') {
+      return document.querySelectorAll(dropzone);
+    } else if (dropzone instanceof NodeList || dropzone instanceof Array) {
+      return dropzone;
+    } else if (typeof dropzone === 'function') {
+      return dropzone();
+    } else {
+      return [];
+    }
+  }
+}
+exports.default = Droppable;
+
+/***/ }),
+/* 35 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.DroppableStopEvent = exports.DroppableReturnedEvent = exports.DroppableDroppedEvent = exports.DroppableStartEvent = exports.DroppableEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base droppable event
+ * @class DroppableEvent
+ * @module DroppableEvent
+ * @extends AbstractEvent
+ */
+class DroppableEvent extends _AbstractEvent2.default {
+
+  /**
+   * Original drag event that triggered this droppable event
+   * @property dragEvent
+   * @type {DragEvent}
+   * @readonly
+   */
+  get dragEvent() {
+    return this.data.dragEvent;
+  }
+}
+
+exports.DroppableEvent = DroppableEvent; /**
+                                          * Droppable start event
+                                          * @class DroppableStartEvent
+                                          * @module DroppableStartEvent
+                                          * @extends DroppableEvent
+                                          */
+
+DroppableEvent.type = 'droppable';
+class DroppableStartEvent extends DroppableEvent {
+
+  /**
+   * The initial dropzone element of the currently dragging draggable element
+   * @property dropzone
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get dropzone() {
+    return this.data.dropzone;
+  }
+}
+
+exports.DroppableStartEvent = DroppableStartEvent; /**
+                                                    * Droppable dropped event
+                                                    * @class DroppableDroppedEvent
+                                                    * @module DroppableDroppedEvent
+                                                    * @extends DroppableEvent
+                                                    */
+
+DroppableStartEvent.type = 'droppable:start';
+DroppableStartEvent.cancelable = true;
+class DroppableDroppedEvent extends DroppableEvent {
+
+  /**
+   * The dropzone element you dropped the draggable element into
+   * @property dropzone
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get dropzone() {
+    return this.data.dropzone;
+  }
+}
+
+exports.DroppableDroppedEvent = DroppableDroppedEvent; /**
+                                                        * Droppable returned event
+                                                        * @class DroppableReturnedEvent
+                                                        * @module DroppableReturnedEvent
+                                                        * @extends DroppableEvent
+                                                        */
+
+DroppableDroppedEvent.type = 'droppable:dropped';
+DroppableDroppedEvent.cancelable = true;
+class DroppableReturnedEvent extends DroppableEvent {
+
+  /**
+   * The dropzone element you dragged away from
+   * @property dropzone
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get dropzone() {
+    return this.data.dropzone;
+  }
+}
+
+exports.DroppableReturnedEvent = DroppableReturnedEvent; /**
+                                                          * Droppable stop event
+                                                          * @class DroppableStopEvent
+                                                          * @module DroppableStopEvent
+                                                          * @extends DroppableEvent
+                                                          */
+
+DroppableReturnedEvent.type = 'droppable:returned';
+DroppableReturnedEvent.cancelable = true;
+class DroppableStopEvent extends DroppableEvent {
+
+  /**
+   * The final dropzone element of the draggable element
+   * @property dropzone
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get dropzone() {
+    return this.data.dropzone;
+  }
+}
+exports.DroppableStopEvent = DroppableStopEvent;
+DroppableStopEvent.type = 'droppable:stop';
+DroppableStopEvent.cancelable = true;
+
+/***/ }),
+/* 36 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _DroppableEvent = __webpack_require__(11);
+
+Object.keys(_DroppableEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _DroppableEvent[key];
+    }
+  });
+});
+
+var _Droppable = __webpack_require__(34);
+
+var _Droppable2 = _interopRequireDefault(_Droppable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Droppable2.default;
+
+/***/ }),
+/* 37 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+/**
+ * The Emitter is a simple emitter class that provides you with `on()`, `off()` and `trigger()` methods
+ * @class Emitter
+ * @module Emitter
+ */
+class Emitter {
+  constructor() {
+    this.callbacks = {};
+  }
+
+  /**
+   * Registers callbacks by event name
+   * @param {String} type
+   * @param {...Function} callbacks
+   */
+  on(type, ...callbacks) {
+    if (!this.callbacks[type]) {
+      this.callbacks[type] = [];
+    }
+
+    this.callbacks[type].push(...callbacks);
+
+    return this;
+  }
+
+  /**
+   * Unregisters callbacks by event name
+   * @param {String} type
+   * @param {Function} callback
+   */
+  off(type, callback) {
+    if (!this.callbacks[type]) {
+      return null;
+    }
+
+    const copy = this.callbacks[type].slice(0);
+
+    for (let i = 0; i < copy.length; i++) {
+      if (callback === copy[i]) {
+        this.callbacks[type].splice(i, 1);
+      }
+    }
+
+    return this;
+  }
+
+  /**
+   * Triggers event callbacks by event object
+   * @param {AbstractEvent} event
+   */
+  trigger(event) {
+    if (!this.callbacks[event.type]) {
+      return null;
+    }
+
+    const callbacks = [...this.callbacks[event.type]];
+    const caughtErrors = [];
+
+    for (let i = callbacks.length - 1; i >= 0; i--) {
+      const callback = callbacks[i];
+
+      try {
+        callback(event);
+      } catch (error) {
+        caughtErrors.push(error);
+      }
+    }
+
+    if (caughtErrors.length) {
+      /* eslint-disable no-console */
+      console.error(`Draggable caught errors while triggering '${event.type}'`, caughtErrors);
+      /* eslint-disable no-console */
+    }
+
+    return this;
+  }
+}
+exports.default = Emitter;
+
+/***/ }),
+/* 38 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _Emitter = __webpack_require__(37);
+
+var _Emitter2 = _interopRequireDefault(_Emitter);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Emitter2.default;
+
+/***/ }),
+/* 39 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _utils = __webpack_require__(2);
+
+var _Plugins = __webpack_require__(12);
+
+var _Emitter = __webpack_require__(38);
+
+var _Emitter2 = _interopRequireDefault(_Emitter);
+
+var _Sensors = __webpack_require__(6);
+
+var _DraggableEvent = __webpack_require__(13);
+
+var _DragEvent = __webpack_require__(14);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onDragStart = Symbol('onDragStart');
+const onDragMove = Symbol('onDragMove');
+const onDragStop = Symbol('onDragStop');
+const onDragPressure = Symbol('onDragPressure');
+
+/**
+ * @const {Object} defaultAnnouncements
+ * @const {Function} defaultAnnouncements['drag:start']
+ * @const {Function} defaultAnnouncements['drag:stop']
+ */
+const defaultAnnouncements = {
+  'drag:start': event => `Picked up ${event.source.textContent.trim() || event.source.id || 'draggable element'}`,
+  'drag:stop': event => `Released ${event.source.textContent.trim() || event.source.id || 'draggable element'}`
+};
+
+const defaultClasses = {
+  'container:dragging': 'draggable-container--is-dragging',
+  'source:dragging': 'draggable-source--is-dragging',
+  'source:placed': 'draggable-source--placed',
+  'container:placed': 'draggable-container--placed',
+  'body:dragging': 'draggable--is-dragging',
+  'draggable:over': 'draggable--over',
+  'container:over': 'draggable-container--over',
+  'source:original': 'draggable--original',
+  mirror: 'draggable-mirror'
+};
+
+const defaultOptions = exports.defaultOptions = {
+  draggable: '.draggable-source',
+  handle: null,
+  delay: 100,
+  distance: 0,
+  placedTimeout: 800,
+  plugins: [],
+  sensors: [],
+  exclude: {
+    plugins: [],
+    sensors: []
+  }
+};
+
+/**
+ * This is the core draggable library that does the heavy lifting
+ * @class Draggable
+ * @module Draggable
+ */
+class Draggable {
+
+  /**
+   * Draggable constructor.
+   * @constructs Draggable
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Draggable containers
+   * @param {Object} options - Options for draggable
+   */
+
+  /**
+   * Default plugins draggable uses
+   * @static
+   * @property {Object} Plugins
+   * @property {Announcement} Plugins.Announcement
+   * @property {Focusable} Plugins.Focusable
+   * @property {Mirror} Plugins.Mirror
+   * @property {Scrollable} Plugins.Scrollable
+   * @type {Object}
+   */
+  constructor(containers = [document.body], options = {}) {
+    /**
+     * Draggable containers
+     * @property containers
+     * @type {HTMLElement[]}
+     */
+    if (containers instanceof NodeList || containers instanceof Array) {
+      this.containers = [...containers];
+    } else if (containers instanceof HTMLElement) {
+      this.containers = [containers];
+    } else {
+      throw new Error('Draggable containers are expected to be of type `NodeList`, `HTMLElement[]` or `HTMLElement`');
+    }
+
+    this.options = _extends({}, defaultOptions, options, {
+      classes: _extends({}, defaultClasses, options.classes || {}),
+      announcements: _extends({}, defaultAnnouncements, options.announcements || {}),
+      exclude: {
+        plugins: options.exclude && options.exclude.plugins || [],
+        sensors: options.exclude && options.exclude.sensors || []
+      }
+    });
+
+    /**
+     * Draggables event emitter
+     * @property emitter
+     * @type {Emitter}
+     */
+    this.emitter = new _Emitter2.default();
+
+    /**
+     * Current drag state
+     * @property dragging
+     * @type {Boolean}
+     */
+    this.dragging = false;
+
+    /**
+     * Active plugins
+     * @property plugins
+     * @type {Plugin[]}
+     */
+    this.plugins = [];
+
+    /**
+     * Active sensors
+     * @property sensors
+     * @type {Sensor[]}
+     */
+    this.sensors = [];
+
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragMove] = this[onDragMove].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+    this[onDragPressure] = this[onDragPressure].bind(this);
+
+    document.addEventListener('drag:start', this[onDragStart], true);
+    document.addEventListener('drag:move', this[onDragMove], true);
+    document.addEventListener('drag:stop', this[onDragStop], true);
+    document.addEventListener('drag:pressure', this[onDragPressure], true);
+
+    const defaultPlugins = Object.values(Draggable.Plugins).filter(Plugin => !this.options.exclude.plugins.includes(Plugin));
+    const defaultSensors = Object.values(Draggable.Sensors).filter(sensor => !this.options.exclude.sensors.includes(sensor));
+
+    this.addPlugin(...[...defaultPlugins, ...this.options.plugins]);
+    this.addSensor(...[...defaultSensors, ...this.options.sensors]);
+
+    const draggableInitializedEvent = new _DraggableEvent.DraggableInitializedEvent({
+      draggable: this
+    });
+
+    this.on('mirror:created', ({ mirror }) => this.mirror = mirror);
+    this.on('mirror:destroy', () => this.mirror = null);
+
+    this.trigger(draggableInitializedEvent);
+  }
+
+  /**
+   * Destroys Draggable instance. This removes all internal event listeners and
+   * deactivates sensors and plugins
+   */
+
+
+  /**
+   * Default sensors draggable uses
+   * @static
+   * @property {Object} Sensors
+   * @property {MouseSensor} Sensors.MouseSensor
+   * @property {TouchSensor} Sensors.TouchSensor
+   * @type {Object}
+   */
+  destroy() {
+    document.removeEventListener('drag:start', this[onDragStart], true);
+    document.removeEventListener('drag:move', this[onDragMove], true);
+    document.removeEventListener('drag:stop', this[onDragStop], true);
+    document.removeEventListener('drag:pressure', this[onDragPressure], true);
+
+    const draggableDestroyEvent = new _DraggableEvent.DraggableDestroyEvent({
+      draggable: this
+    });
+
+    this.trigger(draggableDestroyEvent);
+
+    this.removePlugin(...this.plugins.map(plugin => plugin.constructor));
+    this.removeSensor(...this.sensors.map(sensor => sensor.constructor));
+  }
+
+  /**
+   * Adds plugin to this draggable instance. This will end up calling the attach method of the plugin
+   * @param {...typeof Plugin} plugins - Plugins that you want attached to draggable
+   * @return {Draggable}
+   * @example draggable.addPlugin(CustomA11yPlugin, CustomMirrorPlugin)
+   */
+  addPlugin(...plugins) {
+    const activePlugins = plugins.map(Plugin => new Plugin(this));
+
+    activePlugins.forEach(plugin => plugin.attach());
+    this.plugins = [...this.plugins, ...activePlugins];
+
+    return this;
+  }
+
+  /**
+   * Removes plugins that are already attached to this draggable instance. This will end up calling
+   * the detach method of the plugin
+   * @param {...typeof Plugin} plugins - Plugins that you want detached from draggable
+   * @return {Draggable}
+   * @example draggable.removePlugin(MirrorPlugin, CustomMirrorPlugin)
+   */
+  removePlugin(...plugins) {
+    const removedPlugins = this.plugins.filter(plugin => plugins.includes(plugin.constructor));
+
+    removedPlugins.forEach(plugin => plugin.detach());
+    this.plugins = this.plugins.filter(plugin => !plugins.includes(plugin.constructor));
+
+    return this;
+  }
+
+  /**
+   * Adds sensors to this draggable instance. This will end up calling the attach method of the sensor
+   * @param {...typeof Sensor} sensors - Sensors that you want attached to draggable
+   * @return {Draggable}
+   * @example draggable.addSensor(ForceTouchSensor, CustomSensor)
+   */
+  addSensor(...sensors) {
+    const activeSensors = sensors.map(Sensor => new Sensor(this.containers, this.options));
+
+    activeSensors.forEach(sensor => sensor.attach());
+    this.sensors = [...this.sensors, ...activeSensors];
+
+    return this;
+  }
+
+  /**
+   * Removes sensors that are already attached to this draggable instance. This will end up calling
+   * the detach method of the sensor
+   * @param {...typeof Sensor} sensors - Sensors that you want attached to draggable
+   * @return {Draggable}
+   * @example draggable.removeSensor(TouchSensor, DragSensor)
+   */
+  removeSensor(...sensors) {
+    const removedSensors = this.sensors.filter(sensor => sensors.includes(sensor.constructor));
+
+    removedSensors.forEach(sensor => sensor.detach());
+    this.sensors = this.sensors.filter(sensor => !sensors.includes(sensor.constructor));
+
+    return this;
+  }
+
+  /**
+   * Adds container to this draggable instance
+   * @param {...HTMLElement} containers - Containers you want to add to draggable
+   * @return {Draggable}
+   * @example draggable.addContainer(document.body)
+   */
+  addContainer(...containers) {
+    this.containers = [...this.containers, ...containers];
+    this.sensors.forEach(sensor => sensor.addContainer(...containers));
+    return this;
+  }
+
+  /**
+   * Removes container from this draggable instance
+   * @param {...HTMLElement} containers - Containers you want to remove from draggable
+   * @return {Draggable}
+   * @example draggable.removeContainer(document.body)
+   */
+  removeContainer(...containers) {
+    this.containers = this.containers.filter(container => !containers.includes(container));
+    this.sensors.forEach(sensor => sensor.removeContainer(...containers));
+    return this;
+  }
+
+  /**
+   * Adds listener for draggable events
+   * @param {String} type - Event name
+   * @param {...Function} callbacks - Event callbacks
+   * @return {Draggable}
+   * @example draggable.on('drag:start', (dragEvent) => dragEvent.cancel());
+   */
+  on(type, ...callbacks) {
+    this.emitter.on(type, ...callbacks);
+    return this;
+  }
+
+  /**
+   * Removes listener from draggable
+   * @param {String} type - Event name
+   * @param {Function} callback - Event callback
+   * @return {Draggable}
+   * @example draggable.off('drag:start', handlerFunction);
+   */
+  off(type, callback) {
+    this.emitter.off(type, callback);
+    return this;
+  }
+
+  /**
+   * Triggers draggable event
+   * @param {AbstractEvent} event - Event instance
+   * @return {Draggable}
+   * @example draggable.trigger(event);
+   */
+  trigger(event) {
+    this.emitter.trigger(event);
+    return this;
+  }
+
+  /**
+   * Returns class name for class identifier
+   * @param {String} name - Name of class identifier
+   * @return {String|null}
+   */
+  getClassNameFor(name) {
+    return this.options.classes[name];
+  }
+
+  /**
+   * Returns true if this draggable instance is currently dragging
+   * @return {Boolean}
+   */
+  isDragging() {
+    return Boolean(this.dragging);
+  }
+
+  /**
+   * Returns all draggable elements
+   * @return {HTMLElement[]}
+   */
+  getDraggableElements() {
+    return this.containers.reduce((current, container) => {
+      return [...current, ...this.getDraggableElementsForContainer(container)];
+    }, []);
+  }
+
+  /**
+   * Returns draggable elements for a given container, excluding the mirror and
+   * original source element if present
+   * @param {HTMLElement} container
+   * @return {HTMLElement[]}
+   */
+  getDraggableElementsForContainer(container) {
+    const allDraggableElements = container.querySelectorAll(this.options.draggable);
+
+    return [...allDraggableElements].filter(childElement => {
+      return childElement !== this.originalSource && childElement !== this.mirror;
+    });
+  }
+
+  /**
+   * Drag start handler
+   * @private
+   * @param {Event} event - DOM Drag event
+   */
+  [onDragStart](event) {
+    const sensorEvent = getSensorEvent(event);
+    const { target, container } = sensorEvent;
+
+    if (!this.containers.includes(container)) {
+      return;
+    }
+
+    if (this.options.handle && target && !(0, _utils.closest)(target, this.options.handle)) {
+      sensorEvent.cancel();
+      return;
+    }
+
+    // Find draggable source element
+    this.originalSource = (0, _utils.closest)(target, this.options.draggable);
+    this.sourceContainer = container;
+
+    if (!this.originalSource) {
+      sensorEvent.cancel();
+      return;
+    }
+
+    if (this.lastPlacedSource && this.lastPlacedContainer) {
+      clearTimeout(this.placedTimeoutID);
+      this.lastPlacedSource.classList.remove(this.getClassNameFor('source:placed'));
+      this.lastPlacedContainer.classList.remove(this.getClassNameFor('container:placed'));
+    }
+
+    this.source = this.originalSource.cloneNode(true);
+    this.originalSource.parentNode.insertBefore(this.source, this.originalSource);
+    this.originalSource.style.display = 'none';
+
+    const dragEvent = new _DragEvent.DragStartEvent({
+      source: this.source,
+      originalSource: this.originalSource,
+      sourceContainer: container,
+      sensorEvent
+    });
+
+    this.trigger(dragEvent);
+
+    this.dragging = !dragEvent.canceled();
+
+    if (dragEvent.canceled()) {
+      this.source.parentNode.removeChild(this.source);
+      this.originalSource.style.display = null;
+      return;
+    }
+
+    this.originalSource.classList.add(this.getClassNameFor('source:original'));
+    this.source.classList.add(this.getClassNameFor('source:dragging'));
+    this.sourceContainer.classList.add(this.getClassNameFor('container:dragging'));
+    document.body.classList.add(this.getClassNameFor('body:dragging'));
+    applyUserSelect(document.body, 'none');
+
+    requestAnimationFrame(() => {
+      const oldSensorEvent = getSensorEvent(event);
+      const newSensorEvent = oldSensorEvent.clone({ target: this.source });
+
+      this[onDragMove](_extends({}, event, {
+        detail: newSensorEvent
+      }));
+    });
+  }
+
+  /**
+   * Drag move handler
+   * @private
+   * @param {Event} event - DOM Drag event
+   */
+  [onDragMove](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    const sensorEvent = getSensorEvent(event);
+    const { container } = sensorEvent;
+    let target = sensorEvent.target;
+
+    const dragMoveEvent = new _DragEvent.DragMoveEvent({
+      source: this.source,
+      originalSource: this.originalSource,
+      sourceContainer: container,
+      sensorEvent
+    });
+
+    this.trigger(dragMoveEvent);
+
+    if (dragMoveEvent.canceled()) {
+      sensorEvent.cancel();
+    }
+
+    target = (0, _utils.closest)(target, this.options.draggable);
+    const withinCorrectContainer = (0, _utils.closest)(sensorEvent.target, this.containers);
+    const overContainer = sensorEvent.overContainer || withinCorrectContainer;
+    const isLeavingContainer = this.currentOverContainer && overContainer !== this.currentOverContainer;
+    const isLeavingDraggable = this.currentOver && target !== this.currentOver;
+    const isOverContainer = overContainer && this.currentOverContainer !== overContainer;
+    const isOverDraggable = withinCorrectContainer && target && this.currentOver !== target;
+
+    if (isLeavingDraggable) {
+      const dragOutEvent = new _DragEvent.DragOutEvent({
+        source: this.source,
+        originalSource: this.originalSource,
+        sourceContainer: container,
+        sensorEvent,
+        over: this.currentOver
+      });
+
+      this.currentOver.classList.remove(this.getClassNameFor('draggable:over'));
+      this.currentOver = null;
+
+      this.trigger(dragOutEvent);
+    }
+
+    if (isLeavingContainer) {
+      const dragOutContainerEvent = new _DragEvent.DragOutContainerEvent({
+        source: this.source,
+        originalSource: this.originalSource,
+        sourceContainer: container,
+        sensorEvent,
+        overContainer: this.currentOverContainer
+      });
+
+      this.currentOverContainer.classList.remove(this.getClassNameFor('container:over'));
+      this.currentOverContainer = null;
+
+      this.trigger(dragOutContainerEvent);
+    }
+
+    if (isOverContainer) {
+      overContainer.classList.add(this.getClassNameFor('container:over'));
+
+      const dragOverContainerEvent = new _DragEvent.DragOverContainerEvent({
+        source: this.source,
+        originalSource: this.originalSource,
+        sourceContainer: container,
+        sensorEvent,
+        overContainer
+      });
+
+      this.currentOverContainer = overContainer;
+
+      this.trigger(dragOverContainerEvent);
+    }
+
+    if (isOverDraggable) {
+      target.classList.add(this.getClassNameFor('draggable:over'));
+
+      const dragOverEvent = new _DragEvent.DragOverEvent({
+        source: this.source,
+        originalSource: this.originalSource,
+        sourceContainer: container,
+        sensorEvent,
+        overContainer,
+        over: target
+      });
+
+      this.currentOver = target;
+
+      this.trigger(dragOverEvent);
+    }
+  }
+
+  /**
+   * Drag stop handler
+   * @private
+   * @param {Event} event - DOM Drag event
+   */
+  [onDragStop](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    this.dragging = false;
+
+    const dragStopEvent = new _DragEvent.DragStopEvent({
+      source: this.source,
+      originalSource: this.originalSource,
+      sensorEvent: event.sensorEvent,
+      sourceContainer: this.sourceContainer
+    });
+
+    this.trigger(dragStopEvent);
+
+    this.source.parentNode.insertBefore(this.originalSource, this.source);
+    this.source.parentNode.removeChild(this.source);
+    this.originalSource.style.display = '';
+
+    this.source.classList.remove(this.getClassNameFor('source:dragging'));
+    this.originalSource.classList.remove(this.getClassNameFor('source:original'));
+    this.originalSource.classList.add(this.getClassNameFor('source:placed'));
+    this.sourceContainer.classList.add(this.getClassNameFor('container:placed'));
+    this.sourceContainer.classList.remove(this.getClassNameFor('container:dragging'));
+    document.body.classList.remove(this.getClassNameFor('body:dragging'));
+    applyUserSelect(document.body, '');
+
+    if (this.currentOver) {
+      this.currentOver.classList.remove(this.getClassNameFor('draggable:over'));
+    }
+
+    if (this.currentOverContainer) {
+      this.currentOverContainer.classList.remove(this.getClassNameFor('container:over'));
+    }
+
+    this.lastPlacedSource = this.originalSource;
+    this.lastPlacedContainer = this.sourceContainer;
+
+    this.placedTimeoutID = setTimeout(() => {
+      if (this.lastPlacedSource) {
+        this.lastPlacedSource.classList.remove(this.getClassNameFor('source:placed'));
+      }
+
+      if (this.lastPlacedContainer) {
+        this.lastPlacedContainer.classList.remove(this.getClassNameFor('container:placed'));
+      }
+
+      this.lastPlacedSource = null;
+      this.lastPlacedContainer = null;
+    }, this.options.placedTimeout);
+
+    this.source = null;
+    this.originalSource = null;
+    this.currentOverContainer = null;
+    this.currentOver = null;
+    this.sourceContainer = null;
+  }
+
+  /**
+   * Drag pressure handler
+   * @private
+   * @param {Event} event - DOM Drag event
+   */
+  [onDragPressure](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    const sensorEvent = getSensorEvent(event);
+    const source = this.source || (0, _utils.closest)(sensorEvent.originalEvent.target, this.options.draggable);
+
+    const dragPressureEvent = new _DragEvent.DragPressureEvent({
+      sensorEvent,
+      source,
+      pressure: sensorEvent.pressure
+    });
+
+    this.trigger(dragPressureEvent);
+  }
+}
+
+exports.default = Draggable;
+Draggable.Plugins = { Announcement: _Plugins.Announcement, Focusable: _Plugins.Focusable, Mirror: _Plugins.Mirror, Scrollable: _Plugins.Scrollable };
+Draggable.Sensors = { MouseSensor: _Sensors.MouseSensor, TouchSensor: _Sensors.TouchSensor };
+function getSensorEvent(event) {
+  return event.detail;
+}
+
+function applyUserSelect(element, value) {
+  element.style.webkitUserSelect = value;
+  element.style.mozUserSelect = value;
+  element.style.msUserSelect = value;
+  element.style.oUserSelect = value;
+  element.style.userSelect = value;
+}
+
+/***/ }),
+/* 40 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _Sensor = __webpack_require__(4);
+
+var _Sensor2 = _interopRequireDefault(_Sensor);
+
+var _SensorEvent = __webpack_require__(3);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onMouseForceWillBegin = Symbol('onMouseForceWillBegin');
+const onMouseForceDown = Symbol('onMouseForceDown');
+const onMouseDown = Symbol('onMouseDown');
+const onMouseForceChange = Symbol('onMouseForceChange');
+const onMouseMove = Symbol('onMouseMove');
+const onMouseUp = Symbol('onMouseUp');
+const onMouseForceGlobalChange = Symbol('onMouseForceGlobalChange');
+
+/**
+ * This sensor picks up native force touch events and dictates drag operations
+ * @class ForceTouchSensor
+ * @module ForceTouchSensor
+ * @extends Sensor
+ */
+class ForceTouchSensor extends _Sensor2.default {
+  /**
+   * ForceTouchSensor constructor.
+   * @constructs ForceTouchSensor
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers
+   * @param {Object} options - Options
+   */
+  constructor(containers = [], options = {}) {
+    super(containers, options);
+
+    /**
+     * Draggable element needs to be remembered to unset the draggable attribute after drag operation has completed
+     * @property mightDrag
+     * @type {Boolean}
+     */
+    this.mightDrag = false;
+
+    this[onMouseForceWillBegin] = this[onMouseForceWillBegin].bind(this);
+    this[onMouseForceDown] = this[onMouseForceDown].bind(this);
+    this[onMouseDown] = this[onMouseDown].bind(this);
+    this[onMouseForceChange] = this[onMouseForceChange].bind(this);
+    this[onMouseMove] = this[onMouseMove].bind(this);
+    this[onMouseUp] = this[onMouseUp].bind(this);
+  }
+
+  /**
+   * Attaches sensors event listeners to the DOM
+   */
+  attach() {
+    for (const container of this.containers) {
+      container.addEventListener('webkitmouseforcewillbegin', this[onMouseForceWillBegin], false);
+      container.addEventListener('webkitmouseforcedown', this[onMouseForceDown], false);
+      container.addEventListener('mousedown', this[onMouseDown], true);
+      container.addEventListener('webkitmouseforcechanged', this[onMouseForceChange], false);
+    }
+
+    document.addEventListener('mousemove', this[onMouseMove]);
+    document.addEventListener('mouseup', this[onMouseUp]);
+  }
+
+  /**
+   * Detaches sensors event listeners to the DOM
+   */
+  detach() {
+    for (const container of this.containers) {
+      container.removeEventListener('webkitmouseforcewillbegin', this[onMouseForceWillBegin], false);
+      container.removeEventListener('webkitmouseforcedown', this[onMouseForceDown], false);
+      container.removeEventListener('mousedown', this[onMouseDown], true);
+      container.removeEventListener('webkitmouseforcechanged', this[onMouseForceChange], false);
+    }
+
+    document.removeEventListener('mousemove', this[onMouseMove]);
+    document.removeEventListener('mouseup', this[onMouseUp]);
+  }
+
+  /**
+   * Mouse force will begin handler
+   * @private
+   * @param {Event} event - Mouse force will begin event
+   */
+  [onMouseForceWillBegin](event) {
+    event.preventDefault();
+    this.mightDrag = true;
+  }
+
+  /**
+   * Mouse force down handler
+   * @private
+   * @param {Event} event - Mouse force down event
+   */
+  [onMouseForceDown](event) {
+    if (this.dragging) {
+      return;
+    }
+
+    const target = document.elementFromPoint(event.clientX, event.clientY);
+    const container = event.currentTarget;
+
+    const dragStartEvent = new _SensorEvent.DragStartSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container,
+      originalEvent: event
+    });
+
+    this.trigger(container, dragStartEvent);
+
+    this.currentContainer = container;
+    this.dragging = !dragStartEvent.canceled();
+    this.mightDrag = false;
+  }
+
+  /**
+   * Mouse up handler
+   * @private
+   * @param {Event} event - Mouse up event
+   */
+  [onMouseUp](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    const dragStopEvent = new _SensorEvent.DragStopSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target: null,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    this.trigger(this.currentContainer, dragStopEvent);
+
+    this.currentContainer = null;
+    this.dragging = false;
+    this.mightDrag = false;
+  }
+
+  /**
+   * Mouse down handler
+   * @private
+   * @param {Event} event - Mouse down event
+   */
+  [onMouseDown](event) {
+    if (!this.mightDrag) {
+      return;
+    }
+
+    // Need workaround for real click
+    // Cancel potential drag events
+    event.stopPropagation();
+    event.stopImmediatePropagation();
+    event.preventDefault();
+  }
+
+  /**
+   * Mouse move handler
+   * @private
+   * @param {Event} event - Mouse force will begin event
+   */
+  [onMouseMove](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    const target = document.elementFromPoint(event.clientX, event.clientY);
+
+    const dragMoveEvent = new _SensorEvent.DragMoveSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    this.trigger(this.currentContainer, dragMoveEvent);
+  }
+
+  /**
+   * Mouse force change handler
+   * @private
+   * @param {Event} event - Mouse force change event
+   */
+  [onMouseForceChange](event) {
+    if (this.dragging) {
+      return;
+    }
+
+    const target = event.target;
+    const container = event.currentTarget;
+
+    const dragPressureEvent = new _SensorEvent.DragPressureSensorEvent({
+      pressure: event.webkitForce,
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container,
+      originalEvent: event
+    });
+
+    this.trigger(container, dragPressureEvent);
+  }
+
+  /**
+   * Mouse force global change handler
+   * @private
+   * @param {Event} event - Mouse force global change event
+   */
+  [onMouseForceGlobalChange](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    const target = event.target;
+
+    const dragPressureEvent = new _SensorEvent.DragPressureSensorEvent({
+      pressure: event.webkitForce,
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    this.trigger(this.currentContainer, dragPressureEvent);
+  }
+}
+exports.default = ForceTouchSensor;
+
+/***/ }),
+/* 41 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _ForceTouchSensor = __webpack_require__(40);
+
+var _ForceTouchSensor2 = _interopRequireDefault(_ForceTouchSensor);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _ForceTouchSensor2.default;
+
+/***/ }),
+/* 42 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _utils = __webpack_require__(2);
+
+var _Sensor = __webpack_require__(4);
+
+var _Sensor2 = _interopRequireDefault(_Sensor);
+
+var _SensorEvent = __webpack_require__(3);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onMouseDown = Symbol('onMouseDown');
+const onMouseUp = Symbol('onMouseUp');
+const onDragStart = Symbol('onDragStart');
+const onDragOver = Symbol('onDragOver');
+const onDragEnd = Symbol('onDragEnd');
+const onDrop = Symbol('onDrop');
+const reset = Symbol('reset');
+
+/**
+ * This sensor picks up native browser drag events and dictates drag operations
+ * @class DragSensor
+ * @module DragSensor
+ * @extends Sensor
+ */
+class DragSensor extends _Sensor2.default {
+  /**
+   * DragSensor constructor.
+   * @constructs DragSensor
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers
+   * @param {Object} options - Options
+   */
+  constructor(containers = [], options = {}) {
+    super(containers, options);
+
+    /**
+     * Mouse down timer which will end up setting the draggable attribute, unless canceled
+     * @property mouseDownTimeout
+     * @type {Number}
+     */
+    this.mouseDownTimeout = null;
+
+    /**
+     * Draggable element needs to be remembered to unset the draggable attribute after drag operation has completed
+     * @property draggableElement
+     * @type {HTMLElement}
+     */
+    this.draggableElement = null;
+
+    /**
+     * Native draggable element could be links or images, their draggable state will be disabled during drag operation
+     * @property nativeDraggableElement
+     * @type {HTMLElement}
+     */
+    this.nativeDraggableElement = null;
+
+    this[onMouseDown] = this[onMouseDown].bind(this);
+    this[onMouseUp] = this[onMouseUp].bind(this);
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragOver] = this[onDragOver].bind(this);
+    this[onDragEnd] = this[onDragEnd].bind(this);
+    this[onDrop] = this[onDrop].bind(this);
+  }
+
+  /**
+   * Attaches sensors event listeners to the DOM
+   */
+  attach() {
+    document.addEventListener('mousedown', this[onMouseDown], true);
+  }
+
+  /**
+   * Detaches sensors event listeners to the DOM
+   */
+  detach() {
+    document.removeEventListener('mousedown', this[onMouseDown], true);
+  }
+
+  /**
+   * Drag start handler
+   * @private
+   * @param {Event} event - Drag start event
+   */
+  [onDragStart](event) {
+    // Need for firefox. "text" key is needed for IE
+    event.dataTransfer.setData('text', '');
+    event.dataTransfer.effectAllowed = this.options.type;
+
+    const target = document.elementFromPoint(event.clientX, event.clientY);
+    this.currentContainer = (0, _utils.closest)(event.target, this.containers);
+
+    if (!this.currentContainer) {
+      return;
+    }
+
+    const dragStartEvent = new _SensorEvent.DragStartSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    // Workaround
+    setTimeout(() => {
+      this.trigger(this.currentContainer, dragStartEvent);
+
+      if (dragStartEvent.canceled()) {
+        this.dragging = false;
+      } else {
+        this.dragging = true;
+      }
+    }, 0);
+  }
+
+  /**
+   * Drag over handler
+   * @private
+   * @param {Event} event - Drag over event
+   */
+  [onDragOver](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    const target = document.elementFromPoint(event.clientX, event.clientY);
+    const container = this.currentContainer;
+
+    const dragMoveEvent = new _SensorEvent.DragMoveSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container,
+      originalEvent: event
+    });
+
+    this.trigger(container, dragMoveEvent);
+
+    if (!dragMoveEvent.canceled()) {
+      event.preventDefault();
+      event.dataTransfer.dropEffect = this.options.type;
+    }
+  }
+
+  /**
+   * Drag end handler
+   * @private
+   * @param {Event} event - Drag end event
+   */
+  [onDragEnd](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    document.removeEventListener('mouseup', this[onMouseUp], true);
+
+    const target = document.elementFromPoint(event.clientX, event.clientY);
+    const container = this.currentContainer;
+
+    const dragStopEvent = new _SensorEvent.DragStopSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container,
+      originalEvent: event
+    });
+
+    this.trigger(container, dragStopEvent);
+
+    this.dragging = false;
+    this.startEvent = null;
+
+    this[reset]();
+  }
+
+  /**
+   * Drop handler
+   * @private
+   * @param {Event} event - Drop event
+   */
+  [onDrop](event) {
+    // eslint-disable-line class-methods-use-this
+    event.preventDefault();
+  }
+
+  /**
+   * Mouse down handler
+   * @private
+   * @param {Event} event - Mouse down event
+   */
+  [onMouseDown](event) {
+    // Firefox bug for inputs within draggables https://bugzilla.mozilla.org/show_bug.cgi?id=739071
+    if (event.target && (event.target.form || event.target.contenteditable)) {
+      return;
+    }
+
+    const nativeDraggableElement = (0, _utils.closest)(event.target, element => element.draggable);
+
+    if (nativeDraggableElement) {
+      nativeDraggableElement.draggable = false;
+      this.nativeDraggableElement = nativeDraggableElement;
+    }
+
+    document.addEventListener('mouseup', this[onMouseUp], true);
+    document.addEventListener('dragstart', this[onDragStart], false);
+    document.addEventListener('dragover', this[onDragOver], false);
+    document.addEventListener('dragend', this[onDragEnd], false);
+    document.addEventListener('drop', this[onDrop], false);
+
+    const target = (0, _utils.closest)(event.target, this.options.draggable);
+
+    if (!target) {
+      return;
+    }
+
+    this.startEvent = event;
+
+    this.mouseDownTimeout = setTimeout(() => {
+      target.draggable = true;
+      this.draggableElement = target;
+    }, this.options.delay);
+  }
+
+  /**
+   * Mouse up handler
+   * @private
+   * @param {Event} event - Mouse up event
+   */
+  [onMouseUp]() {
+    this[reset]();
+  }
+
+  /**
+   * Mouse up handler
+   * @private
+   * @param {Event} event - Mouse up event
+   */
+  [reset]() {
+    clearTimeout(this.mouseDownTimeout);
+
+    document.removeEventListener('mouseup', this[onMouseUp], true);
+    document.removeEventListener('dragstart', this[onDragStart], false);
+    document.removeEventListener('dragover', this[onDragOver], false);
+    document.removeEventListener('dragend', this[onDragEnd], false);
+    document.removeEventListener('drop', this[onDrop], false);
+
+    if (this.nativeDraggableElement) {
+      this.nativeDraggableElement.draggable = true;
+      this.nativeDraggableElement = null;
+    }
+
+    if (this.draggableElement) {
+      this.draggableElement.draggable = false;
+      this.draggableElement = null;
+    }
+  }
+}
+exports.default = DragSensor;
+
+/***/ }),
+/* 43 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _DragSensor = __webpack_require__(42);
+
+var _DragSensor2 = _interopRequireDefault(_DragSensor);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _DragSensor2.default;
+
+/***/ }),
+/* 44 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _utils = __webpack_require__(2);
+
+var _Sensor = __webpack_require__(4);
+
+var _Sensor2 = _interopRequireDefault(_Sensor);
+
+var _SensorEvent = __webpack_require__(3);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onTouchStart = Symbol('onTouchStart');
+const onTouchEnd = Symbol('onTouchEnd');
+const onTouchMove = Symbol('onTouchMove');
+const startDrag = Symbol('startDrag');
+const onDistanceChange = Symbol('onDistanceChange');
+
+/**
+ * Prevents scrolling when set to true
+ * @var {Boolean} preventScrolling
+ */
+let preventScrolling = false;
+
+// WebKit requires cancelable `touchmove` events to be added as early as possible
+window.addEventListener('touchmove', event => {
+  if (!preventScrolling) {
+    return;
+  }
+
+  // Prevent scrolling
+  event.preventDefault();
+}, { passive: false });
+
+/**
+ * This sensor picks up native browser touch events and dictates drag operations
+ * @class TouchSensor
+ * @module TouchSensor
+ * @extends Sensor
+ */
+class TouchSensor extends _Sensor2.default {
+  /**
+   * TouchSensor constructor.
+   * @constructs TouchSensor
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers
+   * @param {Object} options - Options
+   */
+  constructor(containers = [], options = {}) {
+    super(containers, options);
+
+    /**
+     * Closest scrollable container so accidental scroll can cancel long touch
+     * @property currentScrollableParent
+     * @type {HTMLElement}
+     */
+    this.currentScrollableParent = null;
+
+    /**
+     * TimeoutID for managing delay
+     * @property tapTimeout
+     * @type {Number}
+     */
+    this.tapTimeout = null;
+
+    /**
+     * touchMoved indicates if touch has moved during tapTimeout
+     * @property touchMoved
+     * @type {Boolean}
+     */
+    this.touchMoved = false;
+
+    this[onTouchStart] = this[onTouchStart].bind(this);
+    this[onTouchEnd] = this[onTouchEnd].bind(this);
+    this[onTouchMove] = this[onTouchMove].bind(this);
+    this[startDrag] = this[startDrag].bind(this);
+    this[onDistanceChange] = this[onDistanceChange].bind(this);
+  }
+
+  /**
+   * Attaches sensors event listeners to the DOM
+   */
+  attach() {
+    document.addEventListener('touchstart', this[onTouchStart]);
+  }
+
+  /**
+   * Detaches sensors event listeners to the DOM
+   */
+  detach() {
+    document.removeEventListener('touchstart', this[onTouchStart]);
+  }
+
+  /**
+   * Touch start handler
+   * @private
+   * @param {Event} event - Touch start event
+   */
+  [onTouchStart](event) {
+    const container = (0, _utils.closest)(event.target, this.containers);
+
+    if (!container) {
+      return;
+    }
+    const { distance = 0, delay = 0 } = this.options;
+    const { pageX, pageY } = (0, _utils.touchCoords)(event);
+
+    Object.assign(this, { pageX, pageY });
+    this.onTouchStartAt = Date.now();
+    this.startEvent = event;
+    this.currentContainer = container;
+
+    document.addEventListener('touchend', this[onTouchEnd]);
+    document.addEventListener('touchcancel', this[onTouchEnd]);
+    document.addEventListener('touchmove', this[onDistanceChange]);
+    container.addEventListener('contextmenu', onContextMenu);
+
+    if (distance) {
+      preventScrolling = true;
+    }
+
+    this.tapTimeout = window.setTimeout(() => {
+      this[onDistanceChange]({ touches: [{ pageX: this.pageX, pageY: this.pageY }] });
+    }, delay);
+  }
+
+  /**
+   * Start the drag
+   * @private
+   */
+  [startDrag]() {
+    const startEvent = this.startEvent;
+    const container = this.currentContainer;
+    const touch = (0, _utils.touchCoords)(startEvent);
+
+    const dragStartEvent = new _SensorEvent.DragStartSensorEvent({
+      clientX: touch.pageX,
+      clientY: touch.pageY,
+      target: startEvent.target,
+      container,
+      originalEvent: startEvent
+    });
+
+    this.trigger(this.currentContainer, dragStartEvent);
+
+    this.dragging = !dragStartEvent.canceled();
+
+    if (this.dragging) {
+      document.addEventListener('touchmove', this[onTouchMove]);
+    }
+    preventScrolling = this.dragging;
+  }
+
+  /**
+   * Touch move handler prior to drag start.
+   * @private
+   * @param {Event} event - Touch move event
+   */
+  [onDistanceChange](event) {
+    const { delay, distance } = this.options;
+    const { startEvent } = this;
+    const start = (0, _utils.touchCoords)(startEvent);
+    const current = (0, _utils.touchCoords)(event);
+    const timeElapsed = Date.now() - this.onTouchStartAt;
+    const distanceTravelled = (0, _utils.distance)(start.pageX, start.pageY, current.pageX, current.pageY);
+
+    Object.assign(this, current);
+    if (timeElapsed >= delay && distanceTravelled >= distance) {
+      window.clearTimeout(this.tapTimeout);
+      document.removeEventListener('touchmove', this[onDistanceChange]);
+      this[startDrag]();
+    }
+  }
+
+  /**
+   * Mouse move handler while dragging
+   * @private
+   * @param {Event} event - Touch move event
+   */
+  [onTouchMove](event) {
+    if (!this.dragging) {
+      return;
+    }
+    const { pageX, pageY } = (0, _utils.touchCoords)(event);
+    const target = document.elementFromPoint(pageX - window.scrollX, pageY - window.scrollY);
+
+    const dragMoveEvent = new _SensorEvent.DragMoveSensorEvent({
+      clientX: pageX,
+      clientY: pageY,
+      target,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    this.trigger(this.currentContainer, dragMoveEvent);
+  }
+
+  /**
+   * Touch end handler
+   * @private
+   * @param {Event} event - Touch end event
+   */
+  [onTouchEnd](event) {
+    clearTimeout(this.tapTimeout);
+    preventScrolling = false;
+
+    document.removeEventListener('touchend', this[onTouchEnd]);
+    document.removeEventListener('touchcancel', this[onTouchEnd]);
+    document.removeEventListener('touchmove', this[onDistanceChange]);
+
+    if (this.currentContainer) {
+      this.currentContainer.removeEventListener('contextmenu', onContextMenu);
+    }
+
+    if (!this.dragging) {
+      return;
+    }
+
+    document.removeEventListener('touchmove', this[onTouchMove]);
+
+    const { pageX, pageY } = (0, _utils.touchCoords)(event);
+    const target = document.elementFromPoint(pageX - window.scrollX, pageY - window.scrollY);
+
+    event.preventDefault();
+
+    const dragStopEvent = new _SensorEvent.DragStopSensorEvent({
+      clientX: pageX,
+      clientY: pageY,
+      target,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    this.trigger(this.currentContainer, dragStopEvent);
+
+    this.currentContainer = null;
+    this.dragging = false;
+    this.startEvent = null;
+  }
+}
+
+exports.default = TouchSensor;
+function onContextMenu(event) {
+  event.preventDefault();
+  event.stopPropagation();
+}
+
+/***/ }),
+/* 45 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _TouchSensor = __webpack_require__(44);
+
+var _TouchSensor2 = _interopRequireDefault(_TouchSensor);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _TouchSensor2.default;
+
+/***/ }),
+/* 46 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.DragPressureSensorEvent = exports.DragStopSensorEvent = exports.DragMoveSensorEvent = exports.DragStartSensorEvent = exports.SensorEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base sensor event
+ * @class SensorEvent
+ * @module SensorEvent
+ * @extends AbstractEvent
+ */
+class SensorEvent extends _AbstractEvent2.default {
+  /**
+   * Original browser event that triggered a sensor
+   * @property originalEvent
+   * @type {Event}
+   * @readonly
+   */
+  get originalEvent() {
+    return this.data.originalEvent;
+  }
+
+  /**
+   * Normalized clientX for both touch and mouse events
+   * @property clientX
+   * @type {Number}
+   * @readonly
+   */
+  get clientX() {
+    return this.data.clientX;
+  }
+
+  /**
+   * Normalized clientY for both touch and mouse events
+   * @property clientY
+   * @type {Number}
+   * @readonly
+   */
+  get clientY() {
+    return this.data.clientY;
+  }
+
+  /**
+   * Normalized target for both touch and mouse events
+   * Returns the element that is behind cursor or touch pointer
+   * @property target
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get target() {
+    return this.data.target;
+  }
+
+  /**
+   * Container that initiated the sensor
+   * @property container
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get container() {
+    return this.data.container;
+  }
+
+  /**
+   * Trackpad pressure
+   * @property pressure
+   * @type {Number}
+   * @readonly
+   */
+  get pressure() {
+    return this.data.pressure;
+  }
+}
+
+exports.SensorEvent = SensorEvent; /**
+                                    * Drag start sensor event
+                                    * @class DragStartSensorEvent
+                                    * @module DragStartSensorEvent
+                                    * @extends SensorEvent
+                                    */
+
+class DragStartSensorEvent extends SensorEvent {}
+
+exports.DragStartSensorEvent = DragStartSensorEvent; /**
+                                                      * Drag move sensor event
+                                                      * @class DragMoveSensorEvent
+                                                      * @module DragMoveSensorEvent
+                                                      * @extends SensorEvent
+                                                      */
+
+DragStartSensorEvent.type = 'drag:start';
+class DragMoveSensorEvent extends SensorEvent {}
+
+exports.DragMoveSensorEvent = DragMoveSensorEvent; /**
+                                                    * Drag stop sensor event
+                                                    * @class DragStopSensorEvent
+                                                    * @module DragStopSensorEvent
+                                                    * @extends SensorEvent
+                                                    */
+
+DragMoveSensorEvent.type = 'drag:move';
+class DragStopSensorEvent extends SensorEvent {}
+
+exports.DragStopSensorEvent = DragStopSensorEvent; /**
+                                                    * Drag pressure sensor event
+                                                    * @class DragPressureSensorEvent
+                                                    * @module DragPressureSensorEvent
+                                                    * @extends SensorEvent
+                                                    */
+
+DragStopSensorEvent.type = 'drag:stop';
+class DragPressureSensorEvent extends SensorEvent {}
+exports.DragPressureSensorEvent = DragPressureSensorEvent;
+DragPressureSensorEvent.type = 'drag:pressure';
+
+/***/ }),
+/* 47 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _utils = __webpack_require__(2);
+
+var _Sensor = __webpack_require__(4);
+
+var _Sensor2 = _interopRequireDefault(_Sensor);
+
+var _SensorEvent = __webpack_require__(3);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onContextMenuWhileDragging = Symbol('onContextMenuWhileDragging');
+const onMouseDown = Symbol('onMouseDown');
+const onMouseMove = Symbol('onMouseMove');
+const onMouseUp = Symbol('onMouseUp');
+const startDrag = Symbol('startDrag');
+const onDistanceChange = Symbol('onDistanceChange');
+
+/**
+ * This sensor picks up native browser mouse events and dictates drag operations
+ * @class MouseSensor
+ * @module MouseSensor
+ * @extends Sensor
+ */
+class MouseSensor extends _Sensor2.default {
+  /**
+   * MouseSensor constructor.
+   * @constructs MouseSensor
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers
+   * @param {Object} options - Options
+   */
+  constructor(containers = [], options = {}) {
+    super(containers, options);
+
+    /**
+     * Mouse down timer which will end up triggering the drag start operation
+     * @property mouseDownTimeout
+     * @type {Number}
+     */
+    this.mouseDownTimeout = null;
+
+    this[onContextMenuWhileDragging] = this[onContextMenuWhileDragging].bind(this);
+    this[onMouseDown] = this[onMouseDown].bind(this);
+    this[onMouseMove] = this[onMouseMove].bind(this);
+    this[onMouseUp] = this[onMouseUp].bind(this);
+    this[startDrag] = this[startDrag].bind(this);
+    this[onDistanceChange] = this[onDistanceChange].bind(this);
+  }
+
+  /**
+   * Attaches sensors event listeners to the DOM
+   */
+  attach() {
+    document.addEventListener('mousedown', this[onMouseDown], true);
+  }
+
+  /**
+   * Detaches sensors event listeners to the DOM
+   */
+  detach() {
+    document.removeEventListener('mousedown', this[onMouseDown], true);
+  }
+
+  /**
+   * Mouse down handler
+   * @private
+   * @param {Event} event - Mouse down event
+   */
+  [onMouseDown](event) {
+    if (event.button !== 0 || event.ctrlKey || event.metaKey) {
+      return;
+    }
+    const container = (0, _utils.closest)(event.target, this.containers);
+
+    if (!container) {
+      return;
+    }
+
+    const { delay = 0 } = this.options;
+    const { pageX, pageY } = event;
+
+    Object.assign(this, { pageX, pageY });
+    this.onMouseDownAt = Date.now();
+    this.startEvent = event;
+
+    this.currentContainer = container;
+    document.addEventListener('mouseup', this[onMouseUp]);
+    document.addEventListener('dragstart', preventNativeDragStart);
+    document.addEventListener('mousemove', this[onDistanceChange]);
+
+    this.mouseDownTimeout = window.setTimeout(() => {
+      this[onDistanceChange]({ pageX: this.pageX, pageY: this.pageY });
+    }, delay);
+  }
+
+  /**
+   * Start the drag
+   * @private
+   */
+  [startDrag]() {
+    const startEvent = this.startEvent;
+    const container = this.currentContainer;
+
+    const dragStartEvent = new _SensorEvent.DragStartSensorEvent({
+      clientX: startEvent.clientX,
+      clientY: startEvent.clientY,
+      target: startEvent.target,
+      container,
+      originalEvent: startEvent
+    });
+
+    this.trigger(this.currentContainer, dragStartEvent);
+
+    this.dragging = !dragStartEvent.canceled();
+
+    if (this.dragging) {
+      document.addEventListener('contextmenu', this[onContextMenuWhileDragging], true);
+      document.addEventListener('mousemove', this[onMouseMove]);
+    }
+  }
+
+  /**
+   * Detect change in distance, starting drag when both
+   * delay and distance requirements are met
+   * @private
+   * @param {Event} event - Mouse move event
+   */
+  [onDistanceChange](event) {
+    const { pageX, pageY } = event;
+    const { delay, distance } = this.options;
+    const { startEvent } = this;
+
+    Object.assign(this, { pageX, pageY });
+
+    if (!this.currentContainer) {
+      return;
+    }
+
+    const timeElapsed = Date.now() - this.onMouseDownAt;
+    const distanceTravelled = (0, _utils.distance)(startEvent.pageX, startEvent.pageY, pageX, pageY) || 0;
+
+    if (timeElapsed >= delay && distanceTravelled >= distance) {
+      window.clearTimeout(this.mouseDownTimeout);
+      document.removeEventListener('mousemove', this[onDistanceChange]);
+      this[startDrag]();
+    }
+  }
+
+  /**
+   * Mouse move handler
+   * @private
+   * @param {Event} event - Mouse move event
+   */
+  [onMouseMove](event) {
+    if (!this.dragging) {
+      return;
+    }
+
+    const target = document.elementFromPoint(event.clientX, event.clientY);
+
+    const dragMoveEvent = new _SensorEvent.DragMoveSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    this.trigger(this.currentContainer, dragMoveEvent);
+  }
+
+  /**
+   * Mouse up handler
+   * @private
+   * @param {Event} event - Mouse up event
+   */
+  [onMouseUp](event) {
+    clearTimeout(this.mouseDownTimeout);
+
+    if (event.button !== 0) {
+      return;
+    }
+
+    document.removeEventListener('mouseup', this[onMouseUp]);
+    document.removeEventListener('dragstart', preventNativeDragStart);
+    document.removeEventListener('mousemove', this[onDistanceChange]);
+
+    if (!this.dragging) {
+      return;
+    }
+
+    const target = document.elementFromPoint(event.clientX, event.clientY);
+
+    const dragStopEvent = new _SensorEvent.DragStopSensorEvent({
+      clientX: event.clientX,
+      clientY: event.clientY,
+      target,
+      container: this.currentContainer,
+      originalEvent: event
+    });
+
+    this.trigger(this.currentContainer, dragStopEvent);
+
+    document.removeEventListener('contextmenu', this[onContextMenuWhileDragging], true);
+    document.removeEventListener('mousemove', this[onMouseMove]);
+
+    this.currentContainer = null;
+    this.dragging = false;
+    this.startEvent = null;
+  }
+
+  /**
+   * Context menu handler
+   * @private
+   * @param {Event} event - Context menu event
+   */
+  [onContextMenuWhileDragging](event) {
+    event.preventDefault();
+  }
+}
+
+exports.default = MouseSensor;
+function preventNativeDragStart(event) {
+  event.preventDefault();
+}
+
+/***/ }),
+/* 48 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _MouseSensor = __webpack_require__(47);
+
+var _MouseSensor2 = _interopRequireDefault(_MouseSensor);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _MouseSensor2.default;
+
+/***/ }),
+/* 49 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+/**
+ * Base sensor class. Extend from this class to create a new or custom sensor
+ * @class Sensor
+ * @module Sensor
+ */
+class Sensor {
+  /**
+   * Sensor constructor.
+   * @constructs Sensor
+   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers
+   * @param {Object} options - Options
+   */
+  constructor(containers = [], options = {}) {
+    /**
+     * Current containers
+     * @property containers
+     * @type {HTMLElement[]}
+     */
+    this.containers = [...containers];
+
+    /**
+     * Current options
+     * @property options
+     * @type {Object}
+     */
+    this.options = _extends({}, options);
+
+    /**
+     * Current drag state
+     * @property dragging
+     * @type {Boolean}
+     */
+    this.dragging = false;
+
+    /**
+     * Current container
+     * @property currentContainer
+     * @type {HTMLElement}
+     */
+    this.currentContainer = null;
+
+    /**
+     * The event of the initial sensor down
+     * @property startEvent
+     * @type {Event}
+     */
+    this.startEvent = null;
+  }
+
+  /**
+   * Attaches sensors event listeners to the DOM
+   * @return {Sensor}
+   */
+  attach() {
+    return this;
+  }
+
+  /**
+   * Detaches sensors event listeners to the DOM
+   * @return {Sensor}
+   */
+  detach() {
+    return this;
+  }
+
+  /**
+   * Adds container to this sensor instance
+   * @param {...HTMLElement} containers - Containers you want to add to this sensor
+   * @example draggable.addContainer(document.body)
+   */
+  addContainer(...containers) {
+    this.containers = [...this.containers, ...containers];
+  }
+
+  /**
+   * Removes container from this sensor instance
+   * @param {...HTMLElement} containers - Containers you want to remove from this sensor
+   * @example draggable.removeContainer(document.body)
+   */
+  removeContainer(...containers) {
+    this.containers = this.containers.filter(container => !containers.includes(container));
+  }
+
+  /**
+   * Triggers event on target element
+   * @param {HTMLElement} element - Element to trigger event on
+   * @param {SensorEvent} sensorEvent - Sensor event to trigger
+   */
+  trigger(element, sensorEvent) {
+    const event = document.createEvent('Event');
+    event.detail = sensorEvent;
+    event.initEvent(sensorEvent.type, true, true);
+    element.dispatchEvent(event);
+    this.lastEvent = sensorEvent;
+
+    return sensorEvent;
+  }
+}
+exports.default = Sensor;
+
+/***/ }),
+/* 50 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = touchCoords;
+/**
+ * Returns the first touch event found in touches or changedTouches of a touch events.
+ * @param {TouchEvent} event a touch event
+ * @return {Touch} a touch object
+ */
+function touchCoords(event = {}) {
+  const { touches, changedTouches } = event;
+  return touches && touches[0] || changedTouches && changedTouches[0];
+}
+
+/***/ }),
+/* 51 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _touchCoords = __webpack_require__(50);
+
+var _touchCoords2 = _interopRequireDefault(_touchCoords);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _touchCoords2.default;
+
+/***/ }),
+/* 52 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = distance;
+/**
+ * Returns the distance between two points
+ * @param  {Number} x1 The X position of the first point
+ * @param  {Number} y1 The Y position of the first point
+ * @param  {Number} x2 The X position of the second point
+ * @param  {Number} y2 The Y position of the second point
+ * @return {Number}
+ */
+function distance(x1, y1, x2, y2) {
+  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
+}
+
+/***/ }),
+/* 53 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _distance = __webpack_require__(52);
+
+var _distance2 = _interopRequireDefault(_distance);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _distance2.default;
+
+/***/ }),
+/* 54 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = requestNextAnimationFrame;
+function requestNextAnimationFrame(callback) {
+  return requestAnimationFrame(() => {
+    requestAnimationFrame(callback);
+  });
+}
+
+/***/ }),
+/* 55 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _requestNextAnimationFrame = __webpack_require__(54);
+
+var _requestNextAnimationFrame2 = _interopRequireDefault(_requestNextAnimationFrame);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _requestNextAnimationFrame2.default;
+
+/***/ }),
+/* 56 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = closest;
+const matchFunction = Element.prototype.matches || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector;
+
+/**
+ * Get the closest parent element of a given element that matches the given
+ * selector string or matching function
+ *
+ * @param {Element} element The child element to find a parent of
+ * @param {String|Function} selector The string or function to use to match
+ *     the parent element
+ * @return {Element|null}
+ */
+function closest(element, value) {
+  if (!element) {
+    return null;
+  }
+
+  const selector = value;
+  const callback = value;
+  const nodeList = value;
+  const singleElement = value;
+
+  const isSelector = Boolean(typeof value === 'string');
+  const isFunction = Boolean(typeof value === 'function');
+  const isNodeList = Boolean(value instanceof NodeList || value instanceof Array);
+  const isElement = Boolean(value instanceof HTMLElement);
+
+  function conditionFn(currentElement) {
+    if (!currentElement) {
+      return currentElement;
+    } else if (isSelector) {
+      return matchFunction.call(currentElement, selector);
+    } else if (isNodeList) {
+      return [...nodeList].includes(currentElement);
+    } else if (isElement) {
+      return singleElement === currentElement;
+    } else if (isFunction) {
+      return callback(currentElement);
+    } else {
+      return null;
+    }
+  }
+
+  let current = element;
+
+  do {
+    current = current.correspondingUseElement || current.correspondingElement || current;
+
+    if (conditionFn(current)) {
+      return current;
+    }
+
+    current = current.parentNode;
+  } while (current && current !== document.body && current !== document);
+
+  return null;
+}
+
+/***/ }),
+/* 57 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _closest = __webpack_require__(56);
+
+var _closest2 = _interopRequireDefault(_closest);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _closest2.default;
+
+/***/ }),
+/* 58 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = exports.scroll = exports.onDragStop = exports.onDragMove = exports.onDragStart = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+var _utils = __webpack_require__(2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onDragStart = exports.onDragStart = Symbol('onDragStart');
+const onDragMove = exports.onDragMove = Symbol('onDragMove');
+const onDragStop = exports.onDragStop = Symbol('onDragStop');
+const scroll = exports.scroll = Symbol('scroll');
+
+/**
+ * Scrollable default options
+ * @property {Object} defaultOptions
+ * @property {Number} defaultOptions.speed
+ * @property {Number} defaultOptions.sensitivity
+ * @property {HTMLElement[]} defaultOptions.scrollableElements
+ * @type {Object}
+ */
+const defaultOptions = exports.defaultOptions = {
+  speed: 6,
+  sensitivity: 50,
+  scrollableElements: []
+};
+
+/**
+ * Scrollable plugin which scrolls the closest scrollable parent
+ * @class Scrollable
+ * @module Scrollable
+ * @extends AbstractPlugin
+ */
+class Scrollable extends _AbstractPlugin2.default {
+  /**
+   * Scrollable constructor.
+   * @constructs Scrollable
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * Scrollable options
+     * @property {Object} options
+     * @property {Number} options.speed
+     * @property {Number} options.sensitivity
+     * @property {HTMLElement[]} options.scrollableElements
+     * @type {Object}
+     */
+    this.options = _extends({}, defaultOptions, this.getOptions());
+
+    /**
+     * Keeps current mouse position
+     * @property {Object} currentMousePosition
+     * @property {Number} currentMousePosition.clientX
+     * @property {Number} currentMousePosition.clientY
+     * @type {Object|null}
+     */
+    this.currentMousePosition = null;
+
+    /**
+     * Scroll animation frame
+     * @property scrollAnimationFrame
+     * @type {Number|null}
+     */
+    this.scrollAnimationFrame = null;
+
+    /**
+     * Closest scrollable element
+     * @property scrollableElement
+     * @type {HTMLElement|null}
+     */
+    this.scrollableElement = null;
+
+    /**
+     * Animation frame looking for the closest scrollable element
+     * @property findScrollableElementFrame
+     * @type {Number|null}
+     */
+    this.findScrollableElementFrame = null;
+
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragMove] = this[onDragMove].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+    this[scroll] = this[scroll].bind(this);
+  }
+
+  /**
+   * Attaches plugins event listeners
+   */
+  attach() {
+    this.draggable.on('drag:start', this[onDragStart]).on('drag:move', this[onDragMove]).on('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Detaches plugins event listeners
+   */
+  detach() {
+    this.draggable.off('drag:start', this[onDragStart]).off('drag:move', this[onDragMove]).off('drag:stop', this[onDragStop]);
+  }
+
+  /**
+   * Returns options passed through draggable
+   * @return {Object}
+   */
+  getOptions() {
+    return this.draggable.options.scrollable || {};
+  }
+
+  /**
+   * Returns closest scrollable elements by element
+   * @param {HTMLElement} target
+   * @return {HTMLElement}
+   */
+  getScrollableElement(target) {
+    if (this.hasDefinedScrollableElements()) {
+      return (0, _utils.closest)(target, this.options.scrollableElements) || document.documentElement;
+    } else {
+      return closestScrollableElement(target);
+    }
+  }
+
+  /**
+   * Returns true if at least one scrollable element have been defined via options
+   * @param {HTMLElement} target
+   * @return {Boolean}
+   */
+  hasDefinedScrollableElements() {
+    return Boolean(this.options.scrollableElements.length !== 0);
+  }
+
+  /**
+   * Drag start handler. Finds closest scrollable parent in separate frame
+   * @param {DragStartEvent} dragEvent
+   * @private
+   */
+  [onDragStart](dragEvent) {
+    this.findScrollableElementFrame = requestAnimationFrame(() => {
+      this.scrollableElement = this.getScrollableElement(dragEvent.source);
+    });
+  }
+
+  /**
+   * Drag move handler. Remembers mouse position and initiates scrolling
+   * @param {DragMoveEvent} dragEvent
+   * @private
+   */
+  [onDragMove](dragEvent) {
+    this.findScrollableElementFrame = requestAnimationFrame(() => {
+      this.scrollableElement = this.getScrollableElement(dragEvent.sensorEvent.target);
+    });
+
+    if (!this.scrollableElement) {
+      return;
+    }
+
+    const sensorEvent = dragEvent.sensorEvent;
+    const scrollOffset = { x: 0, y: 0 };
+
+    if ('ontouchstart' in window) {
+      scrollOffset.y = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
+      scrollOffset.x = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
+    }
+
+    this.currentMousePosition = {
+      clientX: sensorEvent.clientX - scrollOffset.x,
+      clientY: sensorEvent.clientY - scrollOffset.y
+    };
+
+    this.scrollAnimationFrame = requestAnimationFrame(this[scroll]);
+  }
+
+  /**
+   * Drag stop handler. Cancels scroll animations and resets state
+   * @private
+   */
+  [onDragStop]() {
+    cancelAnimationFrame(this.scrollAnimationFrame);
+    cancelAnimationFrame(this.findScrollableElementFrame);
+
+    this.scrollableElement = null;
+    this.scrollAnimationFrame = null;
+    this.findScrollableElementFrame = null;
+    this.currentMousePosition = null;
+  }
+
+  /**
+   * Scroll function that does the heavylifting
+   * @private
+   */
+  [scroll]() {
+    if (!this.scrollableElement || !this.currentMousePosition) {
+      return;
+    }
+
+    cancelAnimationFrame(this.scrollAnimationFrame);
+
+    const { speed, sensitivity } = this.options;
+
+    const rect = this.scrollableElement.getBoundingClientRect();
+    const bottomCutOff = rect.bottom > window.innerHeight;
+    const topCutOff = rect.top < 0;
+    const cutOff = topCutOff || bottomCutOff;
+
+    const documentScrollingElement = getDocumentScrollingElement();
+    const scrollableElement = this.scrollableElement;
+    const clientX = this.currentMousePosition.clientX;
+    const clientY = this.currentMousePosition.clientY;
+
+    if (scrollableElement !== document.body && scrollableElement !== document.documentElement && !cutOff) {
+      const { offsetHeight, offsetWidth } = scrollableElement;
+
+      if (rect.top + offsetHeight - clientY < sensitivity) {
+        scrollableElement.scrollTop += speed;
+      } else if (clientY - rect.top < sensitivity) {
+        scrollableElement.scrollTop -= speed;
+      }
+
+      if (rect.left + offsetWidth - clientX < sensitivity) {
+        scrollableElement.scrollLeft += speed;
+      } else if (clientX - rect.left < sensitivity) {
+        scrollableElement.scrollLeft -= speed;
+      }
+    } else {
+      const { innerHeight, innerWidth } = window;
+
+      if (clientY < sensitivity) {
+        documentScrollingElement.scrollTop -= speed;
+      } else if (innerHeight - clientY < sensitivity) {
+        documentScrollingElement.scrollTop += speed;
+      }
+
+      if (clientX < sensitivity) {
+        documentScrollingElement.scrollLeft -= speed;
+      } else if (innerWidth - clientX < sensitivity) {
+        documentScrollingElement.scrollLeft += speed;
+      }
+    }
+
+    this.scrollAnimationFrame = requestAnimationFrame(this[scroll]);
+  }
+}
+
+exports.default = Scrollable; /**
+                               * Returns true if the passed element has overflow
+                               * @param {HTMLElement} element
+                               * @return {Boolean}
+                               * @private
+                               */
+
+function hasOverflow(element) {
+  const overflowRegex = /(auto|scroll)/;
+  const computedStyles = getComputedStyle(element, null);
+
+  const overflow = computedStyles.getPropertyValue('overflow') + computedStyles.getPropertyValue('overflow-y') + computedStyles.getPropertyValue('overflow-x');
+
+  return overflowRegex.test(overflow);
+}
+
+/**
+ * Returns true if the passed element is statically positioned
+ * @param {HTMLElement} element
+ * @return {Boolean}
+ * @private
+ */
+function isStaticallyPositioned(element) {
+  const position = getComputedStyle(element).getPropertyValue('position');
+  return position === 'static';
+}
+
+/**
+ * Finds closest scrollable element
+ * @param {HTMLElement} element
+ * @return {HTMLElement}
+ * @private
+ */
+function closestScrollableElement(element) {
+  if (!element) {
+    return getDocumentScrollingElement();
+  }
+
+  const position = getComputedStyle(element).getPropertyValue('position');
+  const excludeStaticParents = position === 'absolute';
+
+  const scrollableElement = (0, _utils.closest)(element, parent => {
+    if (excludeStaticParents && isStaticallyPositioned(parent)) {
+      return false;
+    }
+    return hasOverflow(parent);
+  });
+
+  if (position === 'fixed' || !scrollableElement) {
+    return getDocumentScrollingElement();
+  } else {
+    return scrollableElement;
+  }
+}
+
+/**
+ * Returns element that scrolls document
+ * @return {HTMLElement}
+ * @private
+ */
+function getDocumentScrollingElement() {
+  return document.scrollingElement || document.documentElement;
+}
+
+/***/ }),
+/* 59 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _Scrollable = __webpack_require__(58);
+
+var _Scrollable2 = _interopRequireDefault(_Scrollable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Scrollable2.default;
+exports.defaultOptions = _Scrollable.defaultOptions;
+
+/***/ }),
+/* 60 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.MirrorDestroyEvent = exports.MirrorMoveEvent = exports.MirrorAttachedEvent = exports.MirrorCreatedEvent = exports.MirrorCreateEvent = exports.MirrorEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base mirror event
+ * @class MirrorEvent
+ * @module MirrorEvent
+ * @extends AbstractEvent
+ */
+class MirrorEvent extends _AbstractEvent2.default {
+  /**
+   * Draggables source element
+   * @property source
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get source() {
+    return this.data.source;
+  }
+
+  /**
+   * Draggables original source element
+   * @property originalSource
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get originalSource() {
+    return this.data.originalSource;
+  }
+
+  /**
+   * Draggables source container element
+   * @property sourceContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get sourceContainer() {
+    return this.data.sourceContainer;
+  }
+
+  /**
+   * Sensor event
+   * @property sensorEvent
+   * @type {SensorEvent}
+   * @readonly
+   */
+  get sensorEvent() {
+    return this.data.sensorEvent;
+  }
+
+  /**
+   * Drag event
+   * @property dragEvent
+   * @type {DragEvent}
+   * @readonly
+   */
+  get dragEvent() {
+    return this.data.dragEvent;
+  }
+
+  /**
+   * Original event that triggered sensor event
+   * @property originalEvent
+   * @type {Event}
+   * @readonly
+   */
+  get originalEvent() {
+    if (this.sensorEvent) {
+      return this.sensorEvent.originalEvent;
+    }
+
+    return null;
+  }
+}
+
+exports.MirrorEvent = MirrorEvent; /**
+                                    * Mirror create event
+                                    * @class MirrorCreateEvent
+                                    * @module MirrorCreateEvent
+                                    * @extends MirrorEvent
+                                    */
+
+class MirrorCreateEvent extends MirrorEvent {}
+
+exports.MirrorCreateEvent = MirrorCreateEvent; /**
+                                                * Mirror created event
+                                                * @class MirrorCreatedEvent
+                                                * @module MirrorCreatedEvent
+                                                * @extends MirrorEvent
+                                                */
+
+MirrorCreateEvent.type = 'mirror:create';
+class MirrorCreatedEvent extends MirrorEvent {
+
+  /**
+   * Draggables mirror element
+   * @property mirror
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get mirror() {
+    return this.data.mirror;
+  }
+}
+
+exports.MirrorCreatedEvent = MirrorCreatedEvent; /**
+                                                  * Mirror attached event
+                                                  * @class MirrorAttachedEvent
+                                                  * @module MirrorAttachedEvent
+                                                  * @extends MirrorEvent
+                                                  */
+
+MirrorCreatedEvent.type = 'mirror:created';
+class MirrorAttachedEvent extends MirrorEvent {
+
+  /**
+   * Draggables mirror element
+   * @property mirror
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get mirror() {
+    return this.data.mirror;
+  }
+}
+
+exports.MirrorAttachedEvent = MirrorAttachedEvent; /**
+                                                    * Mirror move event
+                                                    * @class MirrorMoveEvent
+                                                    * @module MirrorMoveEvent
+                                                    * @extends MirrorEvent
+                                                    */
+
+MirrorAttachedEvent.type = 'mirror:attached';
+class MirrorMoveEvent extends MirrorEvent {
+
+  /**
+   * Draggables mirror element
+   * @property mirror
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get mirror() {
+    return this.data.mirror;
+  }
+
+  /**
+   * Sensor has exceeded mirror's threshold on x axis
+   * @type {Boolean}
+   * @readonly
+   */
+  get passedThreshX() {
+    return this.data.passedThreshX;
+  }
+
+  /**
+   * Sensor has exceeded mirror's threshold on y axis
+   * @type {Boolean}
+   * @readonly
+   */
+  get passedThreshY() {
+    return this.data.passedThreshY;
+  }
+}
+
+exports.MirrorMoveEvent = MirrorMoveEvent; /**
+                                            * Mirror destroy event
+                                            * @class MirrorDestroyEvent
+                                            * @module MirrorDestroyEvent
+                                            * @extends MirrorEvent
+                                            */
+
+MirrorMoveEvent.type = 'mirror:move';
+MirrorMoveEvent.cancelable = true;
+class MirrorDestroyEvent extends MirrorEvent {
+
+  /**
+   * Draggables mirror element
+   * @property mirror
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get mirror() {
+    return this.data.mirror;
+  }
+}
+exports.MirrorDestroyEvent = MirrorDestroyEvent;
+MirrorDestroyEvent.type = 'mirror:destroy';
+MirrorDestroyEvent.cancelable = true;
+
+/***/ }),
+/* 61 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _MirrorEvent = __webpack_require__(60);
+
+Object.keys(_MirrorEvent).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _MirrorEvent[key];
+    }
+  });
+});
+
+/***/ }),
+/* 62 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = exports.getAppendableContainer = exports.onScroll = exports.onMirrorMove = exports.onMirrorCreated = exports.onDragStop = exports.onDragMove = exports.onDragStart = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+var _MirrorEvent = __webpack_require__(61);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+const onDragStart = exports.onDragStart = Symbol('onDragStart');
+const onDragMove = exports.onDragMove = Symbol('onDragMove');
+const onDragStop = exports.onDragStop = Symbol('onDragStop');
+const onMirrorCreated = exports.onMirrorCreated = Symbol('onMirrorCreated');
+const onMirrorMove = exports.onMirrorMove = Symbol('onMirrorMove');
+const onScroll = exports.onScroll = Symbol('onScroll');
+const getAppendableContainer = exports.getAppendableContainer = Symbol('getAppendableContainer');
+
+/**
+ * Mirror default options
+ * @property {Object} defaultOptions
+ * @property {Boolean} defaultOptions.constrainDimensions
+ * @property {Boolean} defaultOptions.xAxis
+ * @property {Boolean} defaultOptions.yAxis
+ * @property {null} defaultOptions.cursorOffsetX
+ * @property {null} defaultOptions.cursorOffsetY
+ * @type {Object}
+ */
+const defaultOptions = exports.defaultOptions = {
+  constrainDimensions: false,
+  xAxis: true,
+  yAxis: true,
+  cursorOffsetX: null,
+  cursorOffsetY: null,
+  thresholdX: null,
+  thresholdY: null
+};
+
+/**
+ * Mirror plugin which controls the mirror positioning while dragging
+ * @class Mirror
+ * @module Mirror
+ * @extends AbstractPlugin
+ */
+class Mirror extends _AbstractPlugin2.default {
+  /**
+   * Mirror constructor.
+   * @constructs Mirror
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * Mirror options
+     * @property {Object} options
+     * @property {Boolean} options.constrainDimensions
+     * @property {Boolean} options.xAxis
+     * @property {Boolean} options.yAxis
+     * @property {Number|null} options.cursorOffsetX
+     * @property {Number|null} options.cursorOffsetY
+     * @property {String|HTMLElement|Function} options.appendTo
+     * @type {Object}
+     */
+    this.options = _extends({}, defaultOptions, this.getOptions());
+
+    /**
+     * Scroll offset for touch devices because the mirror is positioned fixed
+     * @property {Object} scrollOffset
+     * @property {Number} scrollOffset.x
+     * @property {Number} scrollOffset.y
+     */
+    this.scrollOffset = { x: 0, y: 0 };
+
+    /**
+     * Initial scroll offset for touch devices because the mirror is positioned fixed
+     * @property {Object} scrollOffset
+     * @property {Number} scrollOffset.x
+     * @property {Number} scrollOffset.y
+     */
+    this.initialScrollOffset = {
+      x: window.scrollX,
+      y: window.scrollY
+    };
+
+    this[onDragStart] = this[onDragStart].bind(this);
+    this[onDragMove] = this[onDragMove].bind(this);
+    this[onDragStop] = this[onDragStop].bind(this);
+    this[onMirrorCreated] = this[onMirrorCreated].bind(this);
+    this[onMirrorMove] = this[onMirrorMove].bind(this);
+    this[onScroll] = this[onScroll].bind(this);
+  }
+
+  /**
+   * Attaches plugins event listeners
+   */
+  attach() {
+    this.draggable.on('drag:start', this[onDragStart]).on('drag:move', this[onDragMove]).on('drag:stop', this[onDragStop]).on('mirror:created', this[onMirrorCreated]).on('mirror:move', this[onMirrorMove]);
+  }
+
+  /**
+   * Detaches plugins event listeners
+   */
+  detach() {
+    this.draggable.off('drag:start', this[onDragStart]).off('drag:move', this[onDragMove]).off('drag:stop', this[onDragStop]).off('mirror:created', this[onMirrorCreated]).off('mirror:move', this[onMirrorMove]);
+  }
+
+  /**
+   * Returns options passed through draggable
+   * @return {Object}
+   */
+  getOptions() {
+    return this.draggable.options.mirror || {};
+  }
+
+  [onDragStart](dragEvent) {
+    if (dragEvent.canceled()) {
+      return;
+    }
+
+    if ('ontouchstart' in window) {
+      document.addEventListener('scroll', this[onScroll], true);
+    }
+
+    this.initialScrollOffset = {
+      x: window.scrollX,
+      y: window.scrollY
+    };
+
+    const { source, originalSource, sourceContainer, sensorEvent } = dragEvent;
+
+    // Last sensor position of mirror move
+    this.lastMirrorMovedClient = {
+      x: sensorEvent.clientX,
+      y: sensorEvent.clientY
+    };
+
+    const mirrorCreateEvent = new _MirrorEvent.MirrorCreateEvent({
+      source,
+      originalSource,
+      sourceContainer,
+      sensorEvent,
+      dragEvent
+    });
+
+    this.draggable.trigger(mirrorCreateEvent);
+
+    if (isNativeDragEvent(sensorEvent) || mirrorCreateEvent.canceled()) {
+      return;
+    }
+
+    const appendableContainer = this[getAppendableContainer](source) || sourceContainer;
+    this.mirror = source.cloneNode(true);
+
+    const mirrorCreatedEvent = new _MirrorEvent.MirrorCreatedEvent({
+      source,
+      originalSource,
+      sourceContainer,
+      sensorEvent,
+      dragEvent,
+      mirror: this.mirror
+    });
+
+    const mirrorAttachedEvent = new _MirrorEvent.MirrorAttachedEvent({
+      source,
+      originalSource,
+      sourceContainer,
+      sensorEvent,
+      dragEvent,
+      mirror: this.mirror
+    });
+
+    this.draggable.trigger(mirrorCreatedEvent);
+    appendableContainer.appendChild(this.mirror);
+    this.draggable.trigger(mirrorAttachedEvent);
+  }
+
+  [onDragMove](dragEvent) {
+    if (!this.mirror || dragEvent.canceled()) {
+      return;
+    }
+
+    const { source, originalSource, sourceContainer, sensorEvent } = dragEvent;
+
+    let passedThreshX = true;
+    let passedThreshY = true;
+
+    if (this.options.thresholdX || this.options.thresholdY) {
+      const { x: lastX, y: lastY } = this.lastMirrorMovedClient;
+
+      if (Math.abs(lastX - sensorEvent.clientX) < this.options.thresholdX) {
+        passedThreshX = false;
+      } else {
+        this.lastMirrorMovedClient.x = sensorEvent.clientX;
+      }
+
+      if (Math.abs(lastY - sensorEvent.clientY) < this.options.thresholdY) {
+        passedThreshY = false;
+      } else {
+        this.lastMirrorMovedClient.y = sensorEvent.clientY;
+      }
+
+      if (!passedThreshX && !passedThreshY) {
+        return;
+      }
+    }
+
+    const mirrorMoveEvent = new _MirrorEvent.MirrorMoveEvent({
+      source,
+      originalSource,
+      sourceContainer,
+      sensorEvent,
+      dragEvent,
+      mirror: this.mirror,
+      passedThreshX,
+      passedThreshY
+    });
+
+    this.draggable.trigger(mirrorMoveEvent);
+  }
+
+  [onDragStop](dragEvent) {
+    if ('ontouchstart' in window) {
+      document.removeEventListener('scroll', this[onScroll], true);
+    }
+
+    this.initialScrollOffset = { x: 0, y: 0 };
+    this.scrollOffset = { x: 0, y: 0 };
+
+    if (!this.mirror) {
+      return;
+    }
+
+    const { source, sourceContainer, sensorEvent } = dragEvent;
+
+    const mirrorDestroyEvent = new _MirrorEvent.MirrorDestroyEvent({
+      source,
+      mirror: this.mirror,
+      sourceContainer,
+      sensorEvent,
+      dragEvent
+    });
+
+    this.draggable.trigger(mirrorDestroyEvent);
+
+    if (!mirrorDestroyEvent.canceled()) {
+      this.mirror.parentNode.removeChild(this.mirror);
+    }
+  }
+
+  [onScroll]() {
+    this.scrollOffset = {
+      x: window.scrollX - this.initialScrollOffset.x,
+      y: window.scrollY - this.initialScrollOffset.y
+    };
+  }
+
+  /**
+   * Mirror created handler
+   * @param {MirrorCreatedEvent} mirrorEvent
+   * @return {Promise}
+   * @private
+   */
+  [onMirrorCreated]({ mirror, source, sensorEvent }) {
+    const mirrorClass = this.draggable.getClassNameFor('mirror');
+
+    const setState = (_ref) => {
+      let { mirrorOffset, initialX, initialY } = _ref,
+          args = _objectWithoutProperties(_ref, ['mirrorOffset', 'initialX', 'initialY']);
+
+      this.mirrorOffset = mirrorOffset;
+      this.initialX = initialX;
+      this.initialY = initialY;
+      this.lastMovedX = initialX;
+      this.lastMovedY = initialY;
+      return _extends({ mirrorOffset, initialX, initialY }, args);
+    };
+
+    mirror.style.display = 'none';
+
+    const initialState = {
+      mirror,
+      source,
+      sensorEvent,
+      mirrorClass,
+      scrollOffset: this.scrollOffset,
+      options: this.options,
+      passedThreshX: true,
+      passedThreshY: true
+    };
+
+    return Promise.resolve(initialState)
+    // Fix reflow here
+    .then(computeMirrorDimensions).then(calculateMirrorOffset).then(resetMirror).then(addMirrorClasses).then(positionMirror({ initial: true })).then(removeMirrorID).then(setState);
+  }
+
+  /**
+   * Mirror move handler
+   * @param {MirrorMoveEvent} mirrorEvent
+   * @return {Promise|null}
+   * @private
+   */
+  [onMirrorMove](mirrorEvent) {
+    if (mirrorEvent.canceled()) {
+      return null;
+    }
+
+    const setState = (_ref2) => {
+      let { lastMovedX, lastMovedY } = _ref2,
+          args = _objectWithoutProperties(_ref2, ['lastMovedX', 'lastMovedY']);
+
+      this.lastMovedX = lastMovedX;
+      this.lastMovedY = lastMovedY;
+
+      return _extends({ lastMovedX, lastMovedY }, args);
+    };
+
+    const initialState = {
+      mirror: mirrorEvent.mirror,
+      sensorEvent: mirrorEvent.sensorEvent,
+      mirrorOffset: this.mirrorOffset,
+      options: this.options,
+      initialX: this.initialX,
+      initialY: this.initialY,
+      scrollOffset: this.scrollOffset,
+      passedThreshX: mirrorEvent.passedThreshX,
+      passedThreshY: mirrorEvent.passedThreshY,
+      lastMovedX: this.lastMovedX,
+      lastMovedY: this.lastMovedY
+    };
+
+    return Promise.resolve(initialState).then(positionMirror({ raf: true })).then(setState);
+  }
+
+  /**
+   * Returns appendable container for mirror based on the appendTo option
+   * @private
+   * @param {Object} options
+   * @param {HTMLElement} options.source - Current source
+   * @return {HTMLElement}
+   */
+  [getAppendableContainer](source) {
+    const appendTo = this.options.appendTo;
+
+    if (typeof appendTo === 'string') {
+      return document.querySelector(appendTo);
+    } else if (appendTo instanceof HTMLElement) {
+      return appendTo;
+    } else if (typeof appendTo === 'function') {
+      return appendTo(source);
+    } else {
+      return source.parentNode;
+    }
+  }
+}
+
+exports.default = Mirror; /**
+                           * Computes mirror dimensions based on the source element
+                           * Adds sourceRect to state
+                           * @param {Object} state
+                           * @param {HTMLElement} state.source
+                           * @return {Promise}
+                           * @private
+                           */
+
+function computeMirrorDimensions(_ref3) {
+  let { source } = _ref3,
+      args = _objectWithoutProperties(_ref3, ['source']);
+
+  return withPromise(resolve => {
+    const sourceRect = source.getBoundingClientRect();
+    resolve(_extends({ source, sourceRect }, args));
+  });
+}
+
+/**
+ * Calculates mirror offset
+ * Adds mirrorOffset to state
+ * @param {Object} state
+ * @param {SensorEvent} state.sensorEvent
+ * @param {DOMRect} state.sourceRect
+ * @return {Promise}
+ * @private
+ */
+function calculateMirrorOffset(_ref4) {
+  let { sensorEvent, sourceRect, options } = _ref4,
+      args = _objectWithoutProperties(_ref4, ['sensorEvent', 'sourceRect', 'options']);
+
+  return withPromise(resolve => {
+    const top = options.cursorOffsetY === null ? sensorEvent.clientY - sourceRect.top : options.cursorOffsetY;
+    const left = options.cursorOffsetX === null ? sensorEvent.clientX - sourceRect.left : options.cursorOffsetX;
+
+    const mirrorOffset = { top, left };
+
+    resolve(_extends({ sensorEvent, sourceRect, mirrorOffset, options }, args));
+  });
+}
+
+/**
+ * Applys mirror styles
+ * @param {Object} state
+ * @param {HTMLElement} state.mirror
+ * @param {HTMLElement} state.source
+ * @param {Object} state.options
+ * @return {Promise}
+ * @private
+ */
+function resetMirror(_ref5) {
+  let { mirror, source, options } = _ref5,
+      args = _objectWithoutProperties(_ref5, ['mirror', 'source', 'options']);
+
+  return withPromise(resolve => {
+    let offsetHeight;
+    let offsetWidth;
+
+    if (options.constrainDimensions) {
+      const computedSourceStyles = getComputedStyle(source);
+      offsetHeight = computedSourceStyles.getPropertyValue('height');
+      offsetWidth = computedSourceStyles.getPropertyValue('width');
+    }
+
+    mirror.style.display = null;
+    mirror.style.position = 'fixed';
+    mirror.style.pointerEvents = 'none';
+    mirror.style.top = 0;
+    mirror.style.left = 0;
+    mirror.style.margin = 0;
+
+    if (options.constrainDimensions) {
+      mirror.style.height = offsetHeight;
+      mirror.style.width = offsetWidth;
+    }
+
+    resolve(_extends({ mirror, source, options }, args));
+  });
+}
+
+/**
+ * Applys mirror class on mirror element
+ * @param {Object} state
+ * @param {HTMLElement} state.mirror
+ * @param {String} state.mirrorClass
+ * @return {Promise}
+ * @private
+ */
+function addMirrorClasses(_ref6) {
+  let { mirror, mirrorClass } = _ref6,
+      args = _objectWithoutProperties(_ref6, ['mirror', 'mirrorClass']);
+
+  return withPromise(resolve => {
+    mirror.classList.add(mirrorClass);
+    resolve(_extends({ mirror, mirrorClass }, args));
+  });
+}
+
+/**
+ * Removes source ID from cloned mirror element
+ * @param {Object} state
+ * @param {HTMLElement} state.mirror
+ * @return {Promise}
+ * @private
+ */
+function removeMirrorID(_ref7) {
+  let { mirror } = _ref7,
+      args = _objectWithoutProperties(_ref7, ['mirror']);
+
+  return withPromise(resolve => {
+    mirror.removeAttribute('id');
+    delete mirror.id;
+    resolve(_extends({ mirror }, args));
+  });
+}
+
+/**
+ * Positions mirror with translate3d
+ * @param {Object} state
+ * @param {HTMLElement} state.mirror
+ * @param {SensorEvent} state.sensorEvent
+ * @param {Object} state.mirrorOffset
+ * @param {Number} state.initialY
+ * @param {Number} state.initialX
+ * @param {Object} state.options
+ * @return {Promise}
+ * @private
+ */
+function positionMirror({ withFrame = false, initial = false } = {}) {
+  return (_ref8) => {
+    let {
+      mirror,
+      sensorEvent,
+      mirrorOffset,
+      initialY,
+      initialX,
+      scrollOffset,
+      options,
+      passedThreshX,
+      passedThreshY,
+      lastMovedX,
+      lastMovedY
+    } = _ref8,
+        args = _objectWithoutProperties(_ref8, ['mirror', 'sensorEvent', 'mirrorOffset', 'initialY', 'initialX', 'scrollOffset', 'options', 'passedThreshX', 'passedThreshY', 'lastMovedX', 'lastMovedY']);
+
+    return withPromise(resolve => {
+      const result = _extends({
+        mirror,
+        sensorEvent,
+        mirrorOffset,
+        options
+      }, args);
+
+      if (mirrorOffset) {
+        const x = passedThreshX ? Math.round((sensorEvent.clientX - mirrorOffset.left - scrollOffset.x) / (options.thresholdX || 1)) * (options.thresholdX || 1) : Math.round(lastMovedX);
+        const y = passedThreshY ? Math.round((sensorEvent.clientY - mirrorOffset.top - scrollOffset.y) / (options.thresholdY || 1)) * (options.thresholdY || 1) : Math.round(lastMovedY);
+
+        if (options.xAxis && options.yAxis || initial) {
+          mirror.style.transform = `translate3d(${x}px, ${y}px, 0)`;
+        } else if (options.xAxis && !options.yAxis) {
+          mirror.style.transform = `translate3d(${x}px, ${initialY}px, 0)`;
+        } else if (options.yAxis && !options.xAxis) {
+          mirror.style.transform = `translate3d(${initialX}px, ${y}px, 0)`;
+        }
+
+        if (initial) {
+          result.initialX = x;
+          result.initialY = y;
+        }
+
+        result.lastMovedX = x;
+        result.lastMovedY = y;
+      }
+
+      resolve(result);
+    }, { frame: withFrame });
+  };
+}
+
+/**
+ * Wraps functions in promise with potential animation frame option
+ * @param {Function} callback
+ * @param {Object} options
+ * @param {Boolean} options.raf
+ * @return {Promise}
+ * @private
+ */
+function withPromise(callback, { raf = false } = {}) {
+  return new Promise((resolve, reject) => {
+    if (raf) {
+      requestAnimationFrame(() => {
+        callback(resolve, reject);
+      });
+    } else {
+      callback(resolve, reject);
+    }
+  });
+}
+
+/**
+ * Returns true if the sensor event was triggered by a native browser drag event
+ * @param {SensorEvent} sensorEvent
+ */
+function isNativeDragEvent(sensorEvent) {
+  return (/^drag/.test(sensorEvent.originalEvent.type)
+  );
+}
+
+/***/ }),
+/* 63 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _Mirror = __webpack_require__(62);
+
+var _Mirror2 = _interopRequireDefault(_Mirror);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Mirror2.default;
+exports.defaultOptions = _Mirror.defaultOptions;
+
+/***/ }),
+/* 64 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onInitialize = Symbol('onInitialize');
+const onDestroy = Symbol('onDestroy');
+
+/**
+ * Focusable default options
+ * @property {Object} defaultOptions
+ * @type {Object}
+ */
+const defaultOptions = {};
+
+/**
+ * Focusable plugin
+ * @class Focusable
+ * @module Focusable
+ * @extends AbstractPlugin
+ */
+class Focusable extends _AbstractPlugin2.default {
+  /**
+   * Focusable constructor.
+   * @constructs Focusable
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * Focusable options
+     * @property {Object} options
+     * @type {Object}
+     */
+    this.options = _extends({}, defaultOptions, this.getOptions());
+
+    this[onInitialize] = this[onInitialize].bind(this);
+    this[onDestroy] = this[onDestroy].bind(this);
+  }
+
+  /**
+   * Attaches listeners to draggable
+   */
+  attach() {
+    this.draggable.on('draggable:initialize', this[onInitialize]).on('draggable:destroy', this[onDestroy]);
+  }
+
+  /**
+   * Detaches listeners from draggable
+   */
+  detach() {
+    this.draggable.off('draggable:initialize', this[onInitialize]).off('draggable:destroy', this[onDestroy]);
+
+    // Remove modified elements when detach
+    this[onDestroy]();
+  }
+
+  /**
+   * Returns options passed through draggable
+   * @return {Object}
+   */
+  getOptions() {
+    return this.draggable.options.focusable || {};
+  }
+
+  /**
+   * Returns draggable containers and elements
+   * @return {HTMLElement[]}
+   */
+  getElements() {
+    return [...this.draggable.containers, ...this.draggable.getDraggableElements()];
+  }
+
+  /**
+   * Intialize handler
+   * @private
+   */
+  [onInitialize]() {
+    // Can wait until the next best frame is available
+    requestAnimationFrame(() => {
+      this.getElements().forEach(element => decorateElement(element));
+    });
+  }
+
+  /**
+   * Destroy handler
+   * @private
+   */
+  [onDestroy]() {
+    // Can wait until the next best frame is available
+    requestAnimationFrame(() => {
+      this.getElements().forEach(element => stripElement(element));
+    });
+  }
+}
+
+exports.default = Focusable; /**
+                              * Keeps track of all the elements that are missing tabindex attributes
+                              * so they can be reset when draggable gets destroyed
+                              * @const {HTMLElement[]} elementsWithMissingTabIndex
+                              */
+
+const elementsWithMissingTabIndex = [];
+
+/**
+ * Decorates element with tabindex attributes
+ * @param {HTMLElement} element
+ * @return {Object}
+ * @private
+ */
+function decorateElement(element) {
+  const hasMissingTabIndex = Boolean(!element.getAttribute('tabindex') && element.tabIndex === -1);
+
+  if (hasMissingTabIndex) {
+    elementsWithMissingTabIndex.push(element);
+    element.tabIndex = 0;
+  }
+}
+
+/**
+ * Removes elements tabindex attributes
+ * @param {HTMLElement} element
+ * @private
+ */
+function stripElement(element) {
+  const tabIndexElementPosition = elementsWithMissingTabIndex.indexOf(element);
+
+  if (tabIndexElementPosition !== -1) {
+    element.tabIndex = -1;
+    elementsWithMissingTabIndex.splice(tabIndexElementPosition, 1);
+  }
+}
+
+/***/ }),
+/* 65 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _Focusable = __webpack_require__(64);
+
+var _Focusable2 = _interopRequireDefault(_Focusable);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Focusable2.default;
+
+/***/ }),
+/* 66 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+/**
+ * All draggable plugins inherit from this class.
+ * @abstract
+ * @class AbstractPlugin
+ * @module AbstractPlugin
+ */
+class AbstractPlugin {
+  /**
+   * AbstractPlugin constructor.
+   * @constructs AbstractPlugin
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    /**
+     * Draggable instance
+     * @property draggable
+     * @type {Draggable}
+     */
+    this.draggable = draggable;
+  }
+
+  /**
+   * Override to add listeners
+   * @abstract
+   */
+  attach() {
+    throw new Error('Not Implemented');
+  }
+
+  /**
+   * Override to remove listeners
+   * @abstract
+   */
+  detach() {
+    throw new Error('Not Implemented');
+  }
+}
+exports.default = AbstractPlugin;
+
+/***/ }),
+/* 67 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const onInitialize = Symbol('onInitialize');
+const onDestroy = Symbol('onDestroy');
+const announceEvent = Symbol('announceEvent');
+const announceMessage = Symbol('announceMessage');
+
+const ARIA_RELEVANT = 'aria-relevant';
+const ARIA_ATOMIC = 'aria-atomic';
+const ARIA_LIVE = 'aria-live';
+const ROLE = 'role';
+
+/**
+ * Announcement default options
+ * @property {Object} defaultOptions
+ * @property {Number} defaultOptions.expire
+ * @type {Object}
+ */
+const defaultOptions = exports.defaultOptions = {
+  expire: 7000
+};
+
+/**
+ * Announcement plugin
+ * @class Announcement
+ * @module Announcement
+ * @extends AbstractPlugin
+ */
+class Announcement extends _AbstractPlugin2.default {
+  /**
+   * Announcement constructor.
+   * @constructs Announcement
+   * @param {Draggable} draggable - Draggable instance
+   */
+  constructor(draggable) {
+    super(draggable);
+
+    /**
+     * Plugin options
+     * @property options
+     * @type {Object}
+     */
+    this.options = _extends({}, defaultOptions, this.getOptions());
+
+    /**
+     * Original draggable trigger method. Hack until we have onAll or on('all')
+     * @property originalTriggerMethod
+     * @type {Function}
+     */
+    this.originalTriggerMethod = this.draggable.trigger;
+
+    this[onInitialize] = this[onInitialize].bind(this);
+    this[onDestroy] = this[onDestroy].bind(this);
+  }
+
+  /**
+   * Attaches listeners to draggable
+   */
+  attach() {
+    this.draggable.on('draggable:initialize', this[onInitialize]);
+  }
+
+  /**
+   * Detaches listeners from draggable
+   */
+  detach() {
+    this.draggable.off('draggable:destroy', this[onDestroy]);
+  }
+
+  /**
+   * Returns passed in options
+   */
+  getOptions() {
+    return this.draggable.options.announcements || {};
+  }
+
+  /**
+   * Announces event
+   * @private
+   * @param {AbstractEvent} event
+   */
+  [announceEvent](event) {
+    const message = this.options[event.type];
+
+    if (message && typeof message === 'string') {
+      this[announceMessage](message);
+    }
+
+    if (message && typeof message === 'function') {
+      this[announceMessage](message(event));
+    }
+  }
+
+  /**
+   * Announces message to screen reader
+   * @private
+   * @param {String} message
+   */
+  [announceMessage](message) {
+    announce(message, { expire: this.options.expire });
+  }
+
+  /**
+   * Initialize hander
+   * @private
+   */
+  [onInitialize]() {
+    // Hack until there is an api for listening for all events
+    this.draggable.trigger = event => {
+      try {
+        this[announceEvent](event);
+      } finally {
+        // Ensure that original trigger is called
+        this.originalTriggerMethod.call(this.draggable, event);
+      }
+    };
+  }
+
+  /**
+   * Destroy hander
+   * @private
+   */
+  [onDestroy]() {
+    this.draggable.trigger = this.originalTriggerMethod;
+  }
+}
+
+exports.default = Announcement; /**
+                                 * @const {HTMLElement} liveRegion
+                                 */
+
+const liveRegion = createRegion();
+
+/**
+ * Announces message via live region
+ * @param {String} message
+ * @param {Object} options
+ * @param {Number} options.expire
+ */
+function announce(message, { expire }) {
+  const element = document.createElement('div');
+
+  element.textContent = message;
+  liveRegion.appendChild(element);
+
+  return setTimeout(() => {
+    liveRegion.removeChild(element);
+  }, expire);
+}
+
+/**
+ * Creates region element
+ * @return {HTMLElement}
+ */
+function createRegion() {
+  const element = document.createElement('div');
+
+  element.setAttribute('id', 'draggable-live-region');
+  element.setAttribute(ARIA_RELEVANT, 'additions');
+  element.setAttribute(ARIA_ATOMIC, 'true');
+  element.setAttribute(ARIA_LIVE, 'assertive');
+  element.setAttribute(ROLE, 'log');
+
+  element.style.position = 'fixed';
+  element.style.width = '1px';
+  element.style.height = '1px';
+  element.style.top = '-1px';
+  element.style.overflow = 'hidden';
+
+  return element;
+}
+
+// Append live region element as early as possible
+document.addEventListener('DOMContentLoaded', () => {
+  document.body.appendChild(liveRegion);
+});
+
+/***/ }),
+/* 68 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.defaultOptions = undefined;
+
+var _Announcement = __webpack_require__(67);
+
+var _Announcement2 = _interopRequireDefault(_Announcement);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = _Announcement2.default;
+exports.defaultOptions = _Announcement.defaultOptions;
+
+/***/ }),
+/* 69 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.DraggableDestroyEvent = exports.DraggableInitializedEvent = exports.DraggableEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base draggable event
+ * @class DraggableEvent
+ * @module DraggableEvent
+ * @extends AbstractEvent
+ */
+class DraggableEvent extends _AbstractEvent2.default {
+
+  /**
+   * Draggable instance
+   * @property draggable
+   * @type {Draggable}
+   * @readonly
+   */
+  get draggable() {
+    return this.data.draggable;
+  }
+}
+
+exports.DraggableEvent = DraggableEvent; /**
+                                          * Draggable initialized event
+                                          * @class DraggableInitializedEvent
+                                          * @module DraggableInitializedEvent
+                                          * @extends DraggableEvent
+                                          */
+
+DraggableEvent.type = 'draggable';
+class DraggableInitializedEvent extends DraggableEvent {}
+
+exports.DraggableInitializedEvent = DraggableInitializedEvent; /**
+                                                                * Draggable destory event
+                                                                * @class DraggableInitializedEvent
+                                                                * @module DraggableDestroyEvent
+                                                                * @extends DraggableDestroyEvent
+                                                                */
+
+DraggableInitializedEvent.type = 'draggable:initialize';
+class DraggableDestroyEvent extends DraggableEvent {}
+exports.DraggableDestroyEvent = DraggableDestroyEvent;
+DraggableDestroyEvent.type = 'draggable:destroy';
+
+/***/ }),
+/* 70 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+const canceled = Symbol('canceled');
+
+/**
+ * All events fired by draggable inherit this class. You can call `cancel()` to
+ * cancel a specific event or you can check if an event has been canceled by
+ * calling `canceled()`.
+ * @abstract
+ * @class AbstractEvent
+ * @module AbstractEvent
+ */
+class AbstractEvent {
+
+  /**
+   * AbstractEvent constructor.
+   * @constructs AbstractEvent
+   * @param {object} data - Event data
+   */
+
+  /**
+   * Event type
+   * @static
+   * @abstract
+   * @property type
+   * @type {String}
+   */
+  constructor(data) {
+    this[canceled] = false;
+    this.data = data;
+  }
+
+  /**
+   * Read-only type
+   * @abstract
+   * @return {String}
+   */
+
+
+  /**
+   * Event cancelable
+   * @static
+   * @abstract
+   * @property cancelable
+   * @type {Boolean}
+   */
+  get type() {
+    return this.constructor.type;
+  }
+
+  /**
+   * Read-only cancelable
+   * @abstract
+   * @return {Boolean}
+   */
+  get cancelable() {
+    return this.constructor.cancelable;
+  }
+
+  /**
+   * Cancels the event instance
+   * @abstract
+   */
+  cancel() {
+    this[canceled] = true;
+  }
+
+  /**
+   * Check if event has been canceled
+   * @abstract
+   * @return {Boolean}
+   */
+  canceled() {
+    return Boolean(this[canceled]);
+  }
+
+  /**
+   * Returns new event instance with existing event data.
+   * This method allows for overriding of event data.
+   * @param {Object} data
+   * @return {AbstractEvent}
+   */
+  clone(data) {
+    return new this.constructor(_extends({}, this.data, data));
+  }
+}
+exports.default = AbstractEvent;
+AbstractEvent.type = 'event';
+AbstractEvent.cancelable = false;
+
+/***/ }),
+/* 71 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.DragStopEvent = exports.DragPressureEvent = exports.DragOutContainerEvent = exports.DragOverContainerEvent = exports.DragOutEvent = exports.DragOverEvent = exports.DragMoveEvent = exports.DragStartEvent = exports.DragEvent = undefined;
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Base drag event
+ * @class DragEvent
+ * @module DragEvent
+ * @extends AbstractEvent
+ */
+class DragEvent extends _AbstractEvent2.default {
+
+  /**
+   * Draggables source element
+   * @property source
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get source() {
+    return this.data.source;
+  }
+
+  /**
+   * Draggables original source element
+   * @property originalSource
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get originalSource() {
+    return this.data.originalSource;
+  }
+
+  /**
+   * Draggables mirror element
+   * @property mirror
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get mirror() {
+    return this.data.mirror;
+  }
+
+  /**
+   * Draggables source container element
+   * @property sourceContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get sourceContainer() {
+    return this.data.sourceContainer;
+  }
+
+  /**
+   * Sensor event
+   * @property sensorEvent
+   * @type {SensorEvent}
+   * @readonly
+   */
+  get sensorEvent() {
+    return this.data.sensorEvent;
+  }
+
+  /**
+   * Original event that triggered sensor event
+   * @property originalEvent
+   * @type {Event}
+   * @readonly
+   */
+  get originalEvent() {
+    if (this.sensorEvent) {
+      return this.sensorEvent.originalEvent;
+    }
+
+    return null;
+  }
+}
+
+exports.DragEvent = DragEvent; /**
+                                * Drag start event
+                                * @class DragStartEvent
+                                * @module DragStartEvent
+                                * @extends DragEvent
+                                */
+
+DragEvent.type = 'drag';
+class DragStartEvent extends DragEvent {}
+
+exports.DragStartEvent = DragStartEvent; /**
+                                          * Drag move event
+                                          * @class DragMoveEvent
+                                          * @module DragMoveEvent
+                                          * @extends DragEvent
+                                          */
+
+DragStartEvent.type = 'drag:start';
+DragStartEvent.cancelable = true;
+class DragMoveEvent extends DragEvent {}
+
+exports.DragMoveEvent = DragMoveEvent; /**
+                                        * Drag over event
+                                        * @class DragOverEvent
+                                        * @module DragOverEvent
+                                        * @extends DragEvent
+                                        */
+
+DragMoveEvent.type = 'drag:move';
+class DragOverEvent extends DragEvent {
+
+  /**
+   * Draggable container you are over
+   * @property overContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get overContainer() {
+    return this.data.overContainer;
+  }
+
+  /**
+   * Draggable element you are over
+   * @property over
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get over() {
+    return this.data.over;
+  }
+}
+
+exports.DragOverEvent = DragOverEvent; /**
+                                        * Drag out event
+                                        * @class DragOutEvent
+                                        * @module DragOutEvent
+                                        * @extends DragEvent
+                                        */
+
+DragOverEvent.type = 'drag:over';
+DragOverEvent.cancelable = true;
+class DragOutEvent extends DragEvent {
+
+  /**
+   * Draggable container you are over
+   * @property overContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get overContainer() {
+    return this.data.overContainer;
+  }
+
+  /**
+   * Draggable element you left
+   * @property over
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get over() {
+    return this.data.over;
+  }
+}
+
+exports.DragOutEvent = DragOutEvent; /**
+                                      * Drag over container event
+                                      * @class DragOverContainerEvent
+                                      * @module DragOverContainerEvent
+                                      * @extends DragEvent
+                                      */
+
+DragOutEvent.type = 'drag:out';
+class DragOverContainerEvent extends DragEvent {
+
+  /**
+   * Draggable container you are over
+   * @property overContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get overContainer() {
+    return this.data.overContainer;
+  }
+}
+
+exports.DragOverContainerEvent = DragOverContainerEvent; /**
+                                                          * Drag out container event
+                                                          * @class DragOutContainerEvent
+                                                          * @module DragOutContainerEvent
+                                                          * @extends DragEvent
+                                                          */
+
+DragOverContainerEvent.type = 'drag:over:container';
+class DragOutContainerEvent extends DragEvent {
+
+  /**
+   * Draggable container you left
+   * @property overContainer
+   * @type {HTMLElement}
+   * @readonly
+   */
+  get overContainer() {
+    return this.data.overContainer;
+  }
+}
+
+exports.DragOutContainerEvent = DragOutContainerEvent; /**
+                                                        * Drag pressure event
+                                                        * @class DragPressureEvent
+                                                        * @module DragPressureEvent
+                                                        * @extends DragEvent
+                                                        */
+
+DragOutContainerEvent.type = 'drag:out:container';
+class DragPressureEvent extends DragEvent {
+
+  /**
+   * Pressure applied on draggable element
+   * @property pressure
+   * @type {Number}
+   * @readonly
+   */
+  get pressure() {
+    return this.data.pressure;
+  }
+}
+
+exports.DragPressureEvent = DragPressureEvent; /**
+                                                * Drag stop event
+                                                * @class DragStopEvent
+                                                * @module DragStopEvent
+                                                * @extends DragEvent
+                                                */
+
+DragPressureEvent.type = 'drag:pressure';
+class DragStopEvent extends DragEvent {}
+exports.DragStopEvent = DragStopEvent;
+DragStopEvent.type = 'drag:stop';
+
+/***/ }),
+/* 72 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.Plugins = exports.Sensors = exports.Sortable = exports.Swappable = exports.Droppable = exports.Draggable = exports.BasePlugin = exports.BaseEvent = undefined;
+
+var _Draggable = __webpack_require__(5);
+
+Object.defineProperty(exports, 'Draggable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Draggable).default;
+  }
+});
+
+var _Droppable = __webpack_require__(36);
+
+Object.defineProperty(exports, 'Droppable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Droppable).default;
+  }
+});
+
+var _Swappable = __webpack_require__(33);
+
+Object.defineProperty(exports, 'Swappable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Swappable).default;
+  }
+});
+
+var _Sortable = __webpack_require__(30);
+
+Object.defineProperty(exports, 'Sortable', {
+  enumerable: true,
+  get: function () {
+    return _interopRequireDefault(_Sortable).default;
+  }
+});
+
+var _AbstractEvent = __webpack_require__(1);
+
+var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent);
+
+var _AbstractPlugin = __webpack_require__(0);
+
+var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin);
+
+var _Sensors = __webpack_require__(6);
+
+var Sensors = _interopRequireWildcard(_Sensors);
+
+var _Plugins = __webpack_require__(27);
+
+var Plugins = _interopRequireWildcard(_Plugins);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.BaseEvent = _AbstractEvent2.default;
+exports.BasePlugin = _AbstractPlugin2.default;
+exports.Sensors = Sensors;
+exports.Plugins = Plugins;
+
+/***/ })
+/******/ ]);
+});

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
Moonlight/wwwroot/assets/js/jquery.min.js


Разница между файлами не показана из-за своего большого размера
+ 4 - 0
Moonlight/wwwroot/assets/js/popper.min.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
Moonlight/wwwroot/assets/js/toastr.min.js


+ 8 - 0
Moonlight/wwwroot/assets/js/xterm-addon-fit.min.js

@@ -0,0 +1,8 @@
+/**
+ * Skipped minification because the original files appears to be already minified.
+ * Original file: /npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(function(){return(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{constructor(){}activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()}));
+//# sourceMappingURL=xterm-addon-fit.js.map

Некоторые файлы не были показаны из-за большого количества измененных файлов