From c89609ae61b74210c300fd6a35896dbca1d1be7b Mon Sep 17 00:00:00 2001
From: Andrew <mcdeweykp@gmail.com>
Date: Thu, 10 Feb 2022 15:27:40 -0500
Subject: [PATCH] Inital bedrock support

---
 app/classes/controllers/servers_controller.py |   2 +
 app/classes/minecraft/mc_ping.py              |   8 +-
 app/classes/shared/main_controller.py         | 100 +++-
 app/classes/web/server_handler.py             |   9 +
 .../templates/server/bedrock_wizard.html      | 551 ++++++++++++++++++
 app/frontend/templates/server/wizard.html     |  11 +
 6 files changed, 675 insertions(+), 6 deletions(-)
 create mode 100644 app/frontend/templates/server/bedrock_wizard.html

diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py
index 0e0599c5..bc5475f3 100644
--- a/app/classes/controllers/servers_controller.py
+++ b/app/classes/controllers/servers_controller.py
@@ -28,6 +28,7 @@ class Servers_Controller:
         server_file: str,
         server_log_file: str,
         server_stop: str,
+        server_type: str,
         server_port=25565):
         return servers_helper.create_server(
             name,
@@ -38,6 +39,7 @@ class Servers_Controller:
             server_file,
             server_log_file,
             server_stop,
+            server_type,
             server_port)
 
     @staticmethod
diff --git a/app/classes/minecraft/mc_ping.py b/app/classes/minecraft/mc_ping.py
index 52d3cdec..3f1d34db 100644
--- a/app/classes/minecraft/mc_ping.py
+++ b/app/classes/minecraft/mc_ping.py
@@ -133,7 +133,13 @@ def ping(ip, port):
         sock.connect((ip, port))
 
     except socket.error:
-        return False
+        #We'll try gathering this data on a UDP port next
+        try:
+            sock =socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            sock.connect((ip, port))
+        #If the udp port fails as well we're toast. :sadcat:
+        except socket.error:
+            return False
 
     try:
         host = ip.encode('utf-8')
diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py
index c03ffb1a..cd7904e5 100644
--- a/app/classes/shared/main_controller.py
+++ b/app/classes/shared/main_controller.py
@@ -7,6 +7,7 @@ import tempfile
 from distutils import dir_util
 from typing import Union
 from peewee import DoesNotExist
+import platform
 
 from app.classes.controllers.crafty_perms_controller import Crafty_Perms_Controller
 from app.classes.controllers.management_controller import Management_Controller
@@ -158,6 +159,7 @@ class Controller:
                 logger.warning(f"Failed to copy file with error: {e}")
         #Copy crafty logs to archive dir
         full_log_name = os.path.join(crafty_path, 'logs')
+        print(self.project_root)
         shutil.copytree(os.path.join(self.project_root, 'logs'), full_log_name)
         shutil.make_archive(tempZipStorage, "zip", tempDir)
 
@@ -295,7 +297,7 @@ class Controller:
         # download the jar
         server_jar_obj.download_jar(server, version, full_jar_path)
 
-        new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, port)
+        new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, port, type='minecraft-java')
         return new_id
 
     @staticmethod
@@ -351,7 +353,7 @@ class Controller:
         server_stop = "stop"
 
         new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
-                                      server_log_file, server_stop, port)
+                                      server_log_file, server_stop, port, server_type='minecraft-java')
         return new_id
 
     def import_zip_server(self, server_name: str, zip_path: str, server_jar: str, min_mem: int, max_mem: int, port: int):
@@ -394,9 +396,96 @@ class Controller:
         server_stop = "stop"
 
         new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
-                                      server_log_file, server_stop, port)
+                                      server_log_file, server_stop, port, type='minecraft-java')
         return new_id
 
+#---------------------------------------------------BEDROCK IMPORTS---------------------------------------------------
+
+    def import_bedrock_server(self, server_name: str, server_path: str, server_exe: str, port: int):
+        server_id = helper.create_uuid()
+        new_server_dir = os.path.join(helper.servers_dir, server_id)
+        backup_path = os.path.join(helper.backup_path, server_id)
+        if helper.is_os_windows():
+            new_server_dir = helper.wtol_path(new_server_dir)
+            backup_path = helper.wtol_path(backup_path)
+            new_server_dir.replace(' ', '^ ')
+            backup_path.replace(' ', '^ ')
+
+        helper.ensure_dir_exists(new_server_dir)
+        helper.ensure_dir_exists(backup_path)
+        server_path = helper.get_os_understandable_path(server_path)
+        dir_util.copy_tree(server_path, new_server_dir)
+
+        has_properties = False
+        for item in os.listdir(new_server_dir):
+            if str(item) == 'server.properties':
+                has_properties = True
+        if not has_properties:
+            logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
+            with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
+                f.write(f"server-port={port}")
+                f.close()
+
+        full_jar_path = os.path.join(new_server_dir, server_exe)
+
+        #due to adding strings this must not be an fstring
+        if helper.is_os_windows():
+            server_command = f'"{full_jar_path}"'
+        else:
+            server_command = f'LD_LIBRARY_PATH=. .{full_jar_path}'
+        logger.debug('command: ' + server_command)
+        server_log_file = "N/A"
+        server_stop = "stop"
+
+        new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
+                                      server_log_file, server_stop, port, server_type='minecraft-bedrock')
+        return new_id
+
+    def import_bedrock_zip_server(self, server_name: str, zip_path: str, server_exe: str, port: int):
+        server_id = helper.create_uuid()
+        new_server_dir = os.path.join(helper.servers_dir, server_id)
+        backup_path = os.path.join(helper.backup_path, server_id)
+        if helper.is_os_windows():
+            new_server_dir = helper.wtol_path(new_server_dir)
+            backup_path = helper.wtol_path(backup_path)
+            new_server_dir.replace(' ', '^ ')
+            backup_path.replace(' ', '^ ')
+
+        tempDir = helper.get_os_understandable_path(zip_path)
+        helper.ensure_dir_exists(new_server_dir)
+        helper.ensure_dir_exists(backup_path)
+        has_properties = False
+        #extracts archive to temp directory
+        for item in os.listdir(tempDir):
+            if str(item) == 'server.properties':
+                has_properties = True
+            try:
+                shutil.move(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
+            except Exception as ex:
+                logger.error(f'ERROR IN ZIP IMPORT: {ex}')
+        if not has_properties:
+            logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
+            with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
+                f.write(f"server-port={port}")
+                f.close()
+
+        full_jar_path = os.path.join(new_server_dir, server_exe)
+
+        #due to strings being added we need to leave this as not an fstring
+        if helper.is_os_windows():
+            server_command = f'"{full_jar_path}"'
+        else:
+            server_command = f'LD_LIBRARY_PATH=. .{full_jar_path}'
+        logger.debug('command: ' + server_command)
+        server_log_file = "N/A"
+        server_stop = "stop"
+
+        new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
+                                      server_log_file, server_stop, port, server_type='minecraft-bedrock')
+        return new_id
+
+#---------------------------------------------------END BEDROCK IMPORTS---------------------------------------------------
+
     def rename_backup_dir(self, old_server_id, new_server_id, new_uuid):
         server_data = self.servers.get_server_data_by_id(old_server_id)
         old_bu_path = server_data['backup_path']
@@ -421,11 +510,12 @@ class Controller:
                         server_file: str,
                         server_log_file: str,
                         server_stop: str,
-                        server_port: int):
+                        server_port: int,
+                        server_type: str):
         # put data in the db
 
         new_id = self.servers.create_server(
-            name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
+            name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_type, server_port)
 
         if not helper.check_file_exists(os.path.join(server_dir, "crafty_managed.txt")):
             try:
diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py
index a734dd1f..618c03e4 100644
--- a/app/classes/web/server_handler.py
+++ b/app/classes/web/server_handler.py
@@ -113,6 +113,13 @@ class ServerHandler(BaseHandler):
             page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data())
             template = "server/wizard.html"
 
+        if page == "bedrock_step1":
+            if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
+                self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
+                return
+
+            template = "server/bedrock_wizard.html"
+
         self.render(
             template,
             data=page_data,
@@ -170,6 +177,7 @@ class ServerHandler(BaseHandler):
                     new_executable = server_data.get('executable')
                     new_server_log_file = str(helper.get_os_understandable_path(server_data.get('log_path'))).replace(server_uuid, new_server_uuid)
                     server_port = server_data.get('server_port')
+                    server_type = server_data.get('server_type')
 
                     self.controller.servers.create_server(new_server_name,
                                                           new_server_uuid,
@@ -179,6 +187,7 @@ class ServerHandler(BaseHandler):
                                                           new_executable,
                                                           new_server_log_file,
                                                           stop_command,
+                                                          server_type,
                                                           server_port)
 
                     self.controller.init_all_servers()
diff --git a/app/frontend/templates/server/bedrock_wizard.html b/app/frontend/templates/server/bedrock_wizard.html
new file mode 100644
index 00000000..6ea12c6d
--- /dev/null
+++ b/app/frontend/templates/server/bedrock_wizard.html
@@ -0,0 +1,551 @@
+{% extends ../base.html %}
+
+{% block title %}Crafty Controller - {{ translate('serverWizard', 'newServer', data['lang']) }}{% end %}
+
+{% block content %}
+
+<div class="content-wrapper">
+  <ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
+    <li class="nav-item term-nav-item">
+        <a class="nav-link" href="/server/step1" role="tab" aria-selected="false">
+        <i class="fas fa-file-signature"></i>Minecraft-Java</a>
+    </li>
+    <li class="nav-item term-nav-item">
+      <a class="nav-link active" href="/server/bedrock_step1" role="tab" aria-selected="false">
+        <i class="fas fa-file-signature"></i>Minecraft-Bedrock</a>
+    </li>
+</ul>
+<br>
+<div class="d-none" id="overlay" onclick="hide(event)"></div>
+    <div class="row">
+      <div class="col-sm-6 grid-margin stretch-card">
+        <div class="card">
+          <div class="card-body">
+
+            <h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
+            <br />
+            <p class="card-description">
+
+              <form method="post" class="server-wizard" onSubmit="wait_msg(true)">
+                {% raw xsrf_form_html() %}
+                <input type="hidden" value="import_jar" name="create_type">
+                <div class="row">
+
+                  <div class="col-sm-12">
+                    <div class="form-group">
+                      <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
+                      <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
+                    </div>
+                  </div>
+
+                  <div class="col-sm-12">
+                    <div class="form-group">
+                      <label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
+                      <input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server" required>
+                    </div>
+                  </div>
+
+                  <div class="col-sm-12">
+                    <div class="form-group">
+                      <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
+                      <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
+                    </div>
+                  </div>
+
+
+
+                </div>
+                <br />
+                <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
+                <hr>
+                <div class="row">
+
+                  <div class="col-sm-3">
+                    <div class="form-group">
+                      <label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
+                      <input type="number" class="form-control" id="min_memory2"  name="min_memory" value="1" step="0.5" min="0.5" required>
+                    </div>
+                  </div>
+
+                  <div class="col-sm-3 offset-1">
+                    <div class="form-group">
+                      <label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
+                      <input type="number" class="form-control" id="max_memory2"  name="max_memory" value="2" step="0.5" min="0.5" required>
+                    </div>
+                  </div>
+
+                  <div class="col-sm-3 offset-1">
+                    <div class="form-group">
+                      <label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
+                      <input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1" required>
+                    </div>
+                  </div>
+                  <div class="col-sm-12">
+                    <div class="form-group">
+                      <div id="accordion-2">
+                        <div class="card">
+                          <div class="card-header p-2" id="Role-2">
+                            <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
+                              <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
+                            </p>
+                          </div>
+                          <div id="collapseRole-2" class="collapse" aria-labelledby="Role-2" data-parent="">
+                            <div class="card-body scroll">
+                              <div class="form-group">
+                                {% for r in data['roles'] %}
+                                  <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
+                                  {{ r['role_name'].capitalize() }}</label></span>
+                                {% end %}
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
+                <button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
+
+              </form>
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="col-sm-13 grid-margin stretch-card">
+        <div class="card">
+          <div class="card-body">
+
+            <h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
+            <br />
+            <p class="card-description">
+
+              <form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
+                {% raw xsrf_form_html() %}
+                <input type="hidden" value="import_zip" name="create_type">
+
+                <div class="row">
+                  <div class="col-sm-9">
+                    <div class="col-sm-12">
+                      <div class="form-group">
+                        <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
+                        <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
+                      </div>
+                    </div>
+
+                    <div class="col-sm-12">
+                      <div class="form-group">
+                        <label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
+                        <input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip" required>
+                      </div>
+                    </div>
+
+                    <div class="col-sm-12">
+                      <div class="form-group">
+                        <label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{ translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
+                        <br>
+                        <button class="btn btn-primary mr-2" id="root_files_button" type="button">{{ translate('serverWizard', 'clickRoot', data['lang']) }}</button>
+                      </div>
+                    </div>
+                    
+
+                    <div class="col-sm-12">
+                      <div class="form-group">
+                        <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
+                        <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
+                      </div>
+                    </div>
+                  </div>
+
+
+
+                  <div class="col-sm-3">
+                    <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
+                    <hr>
+                    <div class="row">
+
+                      <div class="col-sm-12">
+                        <div class="form-group">
+                          <label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
+                          <input type="number" class="form-control" id="min_memory3"  name="min_memory" value="1" step="0.5" min="0.5" required>
+                        </div>
+                      </div>
+
+                      <div class="col-sm-12">
+                        <div class="form-group">
+                          <label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
+                          <input type="number" class="form-control" id="max_memory3"  name="max_memory" value="2" step="0.5" min="0.5" required>
+                        </div>
+                      </div>
+
+                      <div class="col-sm-12">
+                        <div class="form-group">
+                          <label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
+                          <input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1" required>
+                        </div>
+                      </div>
+
+                      <div class="col-sm-12">
+                        <div class="form-group">
+                          <div id="accordion-3">
+                            <div class="card">
+                              <div class="card-header p-2" id="Role-3">
+                                <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
+                                  <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
+                                </p>
+                              </div>
+                              <div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
+                                <div class="card-body scroll">
+                                  <div class="form-group">
+                                    {% for r in data['roles'] %}
+                                      <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
+                                      {{ r['role_name'].capitalize() }}</label></span>
+                                    {% end %}
+                                  </div>
+                                </div>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                      <div class="col-sm-12" style="visibility: hidden;">
+                        <div class="form-group">
+                          <input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
+                        </div>
+                      </div>
+                      <div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
+                        <div class="modal-dialog" role="document">
+                          <div class="modal-content">
+                            <div class="modal-header">
+                              <h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard', 'selectZipDir', data['lang']) }}</h5>
+                              <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                                <span aria-hidden="true">&times;</span>
+                              </button>
+                            </div>
+                            <div class="modal-body">
+                              <div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
+                                <input type="radio" id="main-tree-input" name="root_path" value="" checked>
+                                  <span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
+                                    <i class="far fa-folder"></i>
+                                    <i class="far fa-folder-open"></i>
+                                    {{ translate('serverFiles', 'files', data['lang']) }}
+                                    </span>
+                                </input>
+                              </div>
+                            </div>
+                            <div class="modal-footer">
+                              <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverWizard', 'close', data['lang']) }}</button>
+                              <button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                    <button id="zip_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
+                    <button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
+                  </div>
+                </div>
+              </form>
+            </p>
+          </div>
+        </div>
+      </div>
+</div>
+<style>
+.scroll {
+    max-height: 12em;
+    overflow-y: auto;
+}
+.menu-btn {
+    font-size: 0.9em;
+    padding: 2px 10px;
+}
+.menu {
+    padding-top: 10px;
+    z-index: 200;
+    margin-top: 4px;
+    position: absolute;
+    background-color: #2a2c44;
+}
+.menu-option {
+    padding: 6px 20px 6px;
+    color: white;
+}
+#overlay {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 100%;
+    height: 100%;
+    z-index: 100;
+}
+</style>
+<style>
+  /* Remove default bullets */
+ .tree-view,
+ .tree-nested {
+   list-style-type: none;
+   margin: 0;
+   padding: 0;
+   margin-left: 10px;
+ }
+
+ /* Style the items */
+ .tree-item,
+ .files-tree-title {
+   cursor: pointer;
+   user-select: none; /* Prevent text selection */
+ }
+
+ /* Create the caret/arrow with a unicode, and style it */
+ .tree-caret .fa-folder {
+   display: inline-block;
+ }
+ .tree-caret .fa-folder-open {
+   display: none;
+ }
+
+ /* Rotate the caret/arrow icon when clicked on (using JavaScript) */
+ .tree-caret-down .fa-folder {
+   display: none;
+ }
+ .tree-caret-down .fa-folder-open {
+   display: inline-block;
+ }
+
+ /* Hide the nested list */
+ .tree-nested {
+   display: none;
+ }
+</style>
+
+{% end %}
+
+{% block js%}
+<script>
+  document.getElementById("root_files_button").addEventListener("click", function(){
+    if(document.forms["zip"]["server_path"].value != ""){
+      if(document.getElementById('root_files_button').classList.contains('clicked')){
+        document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input" name="root_path" value="" checked><span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate('serverFiles', 'files', data['lang']) }}</span></input>'
+      }else{
+        document.getElementById('root_files_button').classList.add('clicked')
+      }
+    path = document.forms["zip"]["server_path"].value;
+    console.log(document.forms["zip"]["server_path"].value)
+    var token = getCookie("_xsrf");
+    var dialog = bootbox.dialog({
+      message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
+      closeButton: false
+  });
+  setTimeout(function(){
+    dialog.modal('hide');
+}, 2000);
+    $.ajax({
+      type: "POST",
+      headers: {'X-XSRFToken': token},
+      url: '/ajax/unzip_server?id=-1&path='+path,
+    });
+  }else{
+    bootbox.alert("You must input a path before selecting this button");
+  }
+  });
+</script>
+
+<script>
+function dropDown(event) {
+    event.target.parentElement.children[1].classList.remove("d-none");
+    document.getElementById("overlay").classList.remove("d-none");
+}
+function hide(event) {
+    var items = document.getElementsByClassName('menu');
+    for (let i = 0; i < items.length; i++) {
+        items[i].classList.add("d-none");
+    }
+    document.getElementById("overlay").classList.add("d-none");
+}
+  $( document ).ready(function() {
+    console.log('ready');
+    var forms = $('form.server-wizard');
+    forms.each(function(i, formEl) {
+      var form = $(formEl);
+      var min = form.find('[name=min_memory]');
+      var max = form.find('[name=max_memory]');
+      console.log(form, min, max)
+      min.change(function(){
+        check_sizes(max, min, 'min');
+      });
+      max.change(function(){
+        check_sizes(max, min, 'max');
+      });
+    });
+  });
+
+  function wait_msg(importing){
+    bootbox.alert({
+        title: importing ? '{% raw translate("serverWizard", "importing", data['lang']) %}' : '{% raw translate("serverWizard", "downloading", data['lang']) %}',
+        message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data['lang']) %}',
+      });
+  }
+
+  function show_file_tree(){
+    $("#dir_select").modal();
+  }
+
+  function check_sizes(a, b, changed){
+    max_mem = parseFloat(a.val());
+    min_mem = parseFloat(b.val());
+    if (max_mem < min_mem && changed === 'min'){
+      a.val(min_mem)
+    }
+    if (max_mem < min_mem && changed === 'max'){
+      b.val(max_mem)
+    }
+  }
+
+    function getTreeView(path) {
+      document.getElementById('zip_submit').disabled = false;
+      path = path
+
+      $.ajax({
+        type: "GET",
+        url: '/ajax/get_zip_tree?id=-1&path='+path,
+        dataType: 'text',
+        success: function(data){
+          console.log("got response:");
+          console.log(data);
+
+          dataArr = data.split('\n');
+          serverDir = dataArr.shift(); // Remove & return first element (server directory)
+          text = dataArr.join('\n');
+
+          try{
+            document.getElementById('main-tree-div').innerHTML += text;
+            document.getElementById('main-tree').parentElement.classList.add("clicked");
+          }catch{
+            document.getElementById('files-tree').innerHTML = text;
+          }
+          
+
+          document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
+          document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
+
+        },
+      });
+    }
+
+    function getToggleMain(event) {
+      path = event.target.parentElement.getAttribute('data-path');
+          document.getElementById("files-tree").classList.toggle("d-block");
+          document.getElementById(path+"span").classList.toggle("tree-caret-down");
+          document.getElementById(path+"span").classList.toggle("tree-caret");
+      }
+    
+
+    function getDirView(event) {
+        path = event.target.parentElement.getAttribute('data-path');
+
+        if (document.getElementById(path).classList.contains('clicked')){
+
+        var toggler = document.getElementById(path+"span");
+
+        if (toggler.classList.contains('files-tree-title')){
+            document.getElementById(path+"ul").classList.toggle("d-block");
+            document.getElementById(path+"span").classList.toggle("tree-caret-down");
+        }
+        return;
+      }else{
+      $.ajax({
+        type: "GET",
+        url: '/ajax/get_zip_dir?id=-1&path='+path,
+        dataType: 'text',
+        success: function(data){
+          console.log("got response:");
+
+          dataArr = data.split('\n');
+          serverDir = dataArr.shift(); // Remove & return first element (server directory)
+          text = dataArr.join('\n');
+
+          try{
+            document.getElementById(path+"span").classList.add('tree-caret-down');
+            document.getElementById(path).innerHTML += text;
+            document.getElementById(path).classList.add("clicked");
+          }catch{
+            console.log("Bad")
+          }
+
+          var toggler = document.getElementById(path);
+  
+            if (toggler.classList.contains('files-tree-title')){
+            document.getElementById(path+"span").addEventListener("click", function caretListener() {
+              document.getElementById(path+"ul").classList.toggle("d-block");
+              document.getElementById(path+"span").classList.toggle("tree-caret-down");
+            });
+          }
+        },
+      });
+    }
+    }
+    if (webSocket) {
+      webSocket.on('send_temp_path', function (data) {
+        document.getElementById('main-tree-input').setAttribute('value', data.path)
+        getTreeView(data.path);
+        show_file_tree();
+      });
+    }
+
+</script>
+<script type="text/javascript">
+  //<![CDATA[
+  // array of possible countries in the same order as they appear in the country selection list
+
+  function decodeHtmlCharCodes(str) {
+    return str.replace("&quot;", "\"");
+  }
+
+  function convertHtmlJsonToJavacriptArray(str) {
+    var result = []
+    str = decodeHtmlCharCodes(str)
+    for(var i in str)
+      result.push([i, str [i]]);
+    return result
+  }
+
+  var text = '{% raw data["js_server_types"] %}';
+  var serverTypesLists = JSON.parse(text);
+  //convertHtmlJsonToJavacriptArray('{{ data["js_server_types"] }}')
+  /* CountryChange() is called from the onchange event of a select element.
+  * param selectObj - the select object which fired the on change event.
+  */
+  function serverTypeChange(selectObj) {
+    // get the index of the selected option
+    var idx = selectObj.selectedIndex;
+    // get the value of the selected option
+    var which = selectObj.options[idx].value;
+    // use the selected option value to retrieve the list of items from the serverTypesLists array
+    cList = serverTypesLists[which];
+    // get the country select element via its known id
+    var cSelect = document.getElementById("server");
+    // remove the current options from the country select
+    var len=cSelect.options.length;
+    while (cSelect.options.length > 0) {
+      cSelect.remove(0);
+    }
+    var newOption;
+    // create new options ordered by descending
+    for (var i=(cList.length)-1; i>=0; i--) {
+      newOption = document.createElement("option");
+      newOption.value = which+"|"+cList[i];  // assumes option string and value are the same
+      newOption.text=cList[i];
+      // add the new option
+      try {
+        cSelect.add(newOption);  // this will fail in DOM browsers but is needed for IE
+      }
+      catch (e) {
+        cSelect.appendChild(newOption);
+      }
+    }
+  }
+ //]]>
+ </script>
+{% end %}
\ No newline at end of file
diff --git a/app/frontend/templates/server/wizard.html b/app/frontend/templates/server/wizard.html
index 7f82fd29..8e2f1b2a 100644
--- a/app/frontend/templates/server/wizard.html
+++ b/app/frontend/templates/server/wizard.html
@@ -5,6 +5,17 @@
 {% block content %}
 
 <div class="content-wrapper">
+  <div class="content-wrapper">
+    <ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
+      <li class="nav-item term-nav-item">
+          <a class="nav-link active" href="/server/step1" role="tab" aria-selected="false">
+          <i class="fas fa-file-signature"></i>Minecraft-Java</a>
+      </li>
+      <li class="nav-item term-nav-item">
+        <a class="nav-link" href="/server/bedrock_step1" role="tab" aria-selected="false">
+          <i class="fas fa-file-signature"></i>Minecraft-Bedrock</a>
+      </li>
+  </ul>
 <div class="d-none" id="overlay" onclick="hide(event)"></div>
     <div class="row">
       <div class="col-sm-6 grid-margin stretch-card">