Merge branch 'pretzel-fixes' into 'dev'

Fix autostart error with missing param.

See merge request crafty-controller/crafty-commander!100
This commit is contained in:
Andrew 2021-11-28 00:43:18 +00:00
commit f6f52d0f4e
10 changed files with 161 additions and 107 deletions

View File

@ -20,6 +20,7 @@ from app.classes.models.servers import Servers, servers_helper
from app.classes.models.management import management_helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.translation import translation
from app.classes.models.users import users_helper
logger = logging.getLogger(__name__)
@ -145,14 +146,14 @@ class Server:
def run_scheduled_server(self):
console.info("Starting server ID: {} - {}".format(self.server_id, self.name))
logger.info("Starting server {}".format(self.server_id, self.name))
self.run_threaded_server()
self.run_threaded_server(None)
# remove the scheduled job since it's ran
return schedule.CancelJob
def run_threaded_server(self, lang):
def run_threaded_server(self, user_id):
# start the server
self.server_thread = threading.Thread(target=self.start_server, daemon=True, args=(lang,), name='{}_server_thread'.format(self.server_id))
self.server_thread = threading.Thread(target=self.start_server, daemon=True, args=(user_id,), name='{}_server_thread'.format(self.server_id))
self.server_thread.start()
def setup_server_run_command(self):
@ -177,7 +178,11 @@ class Server:
console.warning("Unable to write/access {}".format(self.server_path))
helper.do_exit()
def start_server(self, user_lang):
def start_server(self, user_id):
if not user_id:
user_lang = helper.get_setting('language')
else:
user_lang = users_helper.get_user_lang_by_id(user_id)
logger.info("Start command detected. Reloading settings from DB for server {}".format(self.name))
self.setup_server_run_command()
@ -214,9 +219,14 @@ class Server:
e_flag = False
if e_flag == False:
websocket_helper.broadcast('send_eula_bootbox', {
'id': self.server_id
})
if user_id:
websocket_helper.broadcast_user(user_id, 'send_eula_bootbox', {
'id': self.server_id
})
else:
logger.error("Autostart failed due to EULA being false. Agree not sent due to auto start.")
servers_helper.set_waiting_start(self.server_id, False)
return False
return False
f.close()
@ -235,23 +245,25 @@ class Server:
except Exception as ex:
msg = "Server {} failed to start with error code: {}".format(self.name, ex)
logger.error(msg)
websocket_helper.broadcast('send_start_error', {
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
})
if user_id:
websocket_helper.broadcast_user(user_id, 'send_start_error',{
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
})
return False
if helper.check_internet():
loc_server_port = servers_helper.get_server_stats_by_id(self.server_id)['server_port']
if helper.check_port(loc_server_port):
websocket_helper.broadcast('send_start_reload', {
if user_id:
if helper.check_internet():
loc_server_port = servers_helper.get_server_stats_by_id(self.server_id)['server_port']
if helper.check_port(loc_server_port):
websocket_helper.broadcast_user(user_id, 'send_start_reload', {
})
else:
websocket_helper.broadcast_user(user_id, 'send_start_error', {
'error': translation.translate('error', 'closedPort', user_lang).format(loc_server_port)
})
else:
websocket_helper.broadcast('send_start_error', {
'error': translation.translate('error', 'closedPort', user_lang).format(loc_server_port)
})
else:
websocket_helper.broadcast('send_start_error', {
'error': translation.translate('error', 'internet', user_lang)
})
websocket_helper.broadcast_user(user_id, 'send_start_error', {
'error': translation.translate('error', 'internet', user_lang)
})
servers_helper.set_waiting_start(self.server_id, False)
out_buf = ServerOutBuf(self.process, self.server_id)
@ -322,15 +334,14 @@ class Server:
self.stats.record_stats()
def restart_threaded_server(self, lang):
def restart_threaded_server(self, user_id):
# if not already running, let's just start
if not self.check_running():
self.run_threaded_server(lang)
self.run_threaded_server(user_id)
else:
self.stop_threaded_server()
time.sleep(2)
self.run_threaded_server(lang)
self.run_threaded_server(user_id)
def cleanup_server_object(self):
self.start_time = None
@ -373,7 +384,7 @@ class Server:
if self.settings['crash_detection']:
logger.warning("The server {} has crashed and will be restarted. Restarting server".format(name))
console.warning("The server {} has crashed and will be restarted. Restarting server".format(name))
self.run_threaded_server('en_EN')
self.run_threaded_server(None)
return True
else:
logger.critical(
@ -447,12 +458,12 @@ class Server:
console.info("Removing old crash detection watcher thread")
schedule.clear(self.name)
def agree_eula(self, user_lang):
def agree_eula(self, user_id):
file = os.path.join(self.server_path, 'eula.txt')
f = open(file, 'w')
f.write('eula=true')
f.close
self.run_threaded_server(user_lang)
self.run_threaded_server(user_id)
def is_backup_running(self):
if self.is_backingup:
@ -502,11 +513,14 @@ class Server:
def list_backups(self):
conf = management_helper.get_backup_config(self.server_id)
if helper.check_path_exists(helper.get_os_understandable_path(self.settings['backup_path'])):
files = helper.get_human_readable_files_sizes(helper.list_dir_by_date(helper.get_os_understandable_path(self.settings['backup_path'])))
return [{"path": os.path.relpath(f['path'], start=helper.get_os_understandable_path(conf['backup_path'])), "size": f["size"]} for f in files]
if self.settings['backup_path']:
if helper.check_path_exists(helper.get_os_understandable_path(self.settings['backup_path'])):
files = helper.get_human_readable_files_sizes(helper.list_dir_by_date(helper.get_os_understandable_path(self.settings['backup_path'])))
return [{"path": os.path.relpath(f['path'], start=helper.get_os_understandable_path(conf['backup_path'])), "size": f["size"]} for f in files]
else:
return []
else:
return []
return[]
def jar_update(self):
servers_helper.set_update(self.server_id, True)
@ -535,12 +549,14 @@ class Server:
# There are clients
self.check_update()
message = '<a data-id="'+str(self.server_id)+'" class=""> UPDATING...</i></a>'
websocket_helper.broadcast('update_button_status', {
websocket_helper.broadcast_page('/panel/server_detail', 'update_button_status', {
'isUpdating': self.check_update(),
'server_id': self.server_id,
'wasRunning': wasStarted,
'string': message
})
websocket_helper.broadcast_page('/panel/dashboard', 'send_start_reload', {
})
backup_dir = os.path.join(helper.get_os_understandable_path(self.settings['path']), 'crafty_executable_backups')
#checks if backup directory already exists
if os.path.isdir(backup_dir):
@ -568,7 +584,6 @@ class Server:
while servers_helper.get_server_stats_by_id(self.server_id)['updating']:
if downloaded and not self.is_backingup:
print("Backup Status: " + str(self.is_backingup))
logger.info("Executable updated successfully. Starting Server")
servers_helper.set_update(self.server_id, False)
@ -577,11 +592,13 @@ class Server:
self.check_update()
websocket_helper.broadcast('notification', "Executable update finished for " + self.name)
time.sleep(3)
websocket_helper.broadcast('update_button_status', {
websocket_helper.broadcast_page('/panel/server_detail', 'update_button_status', {
'isUpdating': self.check_update(),
'server_id': self.server_id,
'wasRunning': wasStarted
})
websocket_helper.broadcast_page('/panel/dashboard', 'send_start_reload', {
})
websocket_helper.broadcast('notification', "Executable update finished for "+self.name)
management_helper.add_to_audit_log_raw('Alert', '-1', self.server_id, "Executable update finished for "+self.name, self.settings['server_ip'])

View File

@ -98,17 +98,17 @@ class TasksManager:
for c in commands:
svr = self.controller.get_server_obj(c['server_id']['server_id'])
user_lang = c.get('user')['lang']
user_id = c.get('user')['user_id']
command = c.get('command', None)
if command == 'start_server':
svr.run_threaded_server(user_lang)
svr.run_threaded_server(user_id)
elif command == 'stop_server':
svr.stop_threaded_server()
elif command == "restart_server":
svr.restart_threaded_server(user_lang)
svr.restart_threaded_server(user_id)
elif command == "backup_server":
svr.backup_server()

View File

@ -210,7 +210,7 @@ class AjaxHandler(BaseHandler):
elif page == "eula":
server_id = self.get_argument('id', None)
svr = self.controller.get_server_obj(server_id)
svr.agree_eula(self.controller.users.get_user_lang_by_id(user_data['user_id']))
svr.agree_eula(user_data['user_id'])
@tornado.web.authenticated
def delete(self, page):

View File

@ -647,6 +647,12 @@ class PanelHandler(BaseHandler):
return
server_obj = self.controller.servers.get_server_obj(server_id)
server_settings = self.controller.get_server_data(server_id)
stale_executable = server_obj.executable
#Compares old jar name to page data being passed. If they are different we replace the executable name in the
if str(stale_executable) != str(executable):
execution_command = execution_command.replace(str(stale_executable), str(executable))
server_obj.server_name = server_name
server_obj.path = server_path
server_obj.log_path = log_path
@ -693,7 +699,6 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID")
return
if backup_path is not None:
if enabled == '0':
#TODO Use Controller method
server_obj = self.controller.servers.get_server_obj(server_id)

View File

@ -1,11 +1,13 @@
import json
import logging
import asyncio
import sys
from urllib.parse import parse_qsl
from app.classes.models.users import Users
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.console import console
logger = logging.getLogger(__name__)
@ -31,6 +33,14 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
self.request.remote_ip
return remote_ip
def get_user_id(self):
user_data_cookie_raw = self.get_secure_cookie('user_data')
if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'):
user_data_cookie = user_data_cookie_raw.decode('utf-8')
user_id = json.loads(user_data_cookie)['user_id']
return user_id
def check_auth(self):
user_data_cookie_raw = self.get_secure_cookie('user_data')

View File

@ -42,15 +42,36 @@ class WebSocketHelper:
def filter_fn(client):
return client.page == page
clients = list(filter(filter_fn, self.clients))
self.broadcast_with_fn(filter_fn, event_type, data)
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
def broadcast_user(self, user_id: str, event_type: str, data):
def filter_fn(client):
return client.get_user_id() == user_id
for client in clients:
try:
self.send_message(client, event_type, data)
except Exception as e:
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
self.broadcast_with_fn(filter_fn, event_type, data)
def broadcast_user_page(self, page: str, user_id: str, event_type: str, data):
def filter_fn(client):
if client.get_user_id() != user_id:
return False
if client.page != page:
return False
return True
self.broadcast_with_fn(filter_fn, event_type, data)
def broadcast_user_page_params(self, page: str, params: dict, user_id: str, event_type: str, data):
def filter_fn(client):
if client.get_user_id() != user_id:
return False
if client.page != page:
return False
for key, param in params.items():
if param != client.page_query_params.get(key, None):
return False
return True
self.broadcast_with_fn(filter_fn, event_type, data)
def broadcast_page_params(self, page: str, params: dict, event_type: str, data):
def filter_fn(client):
@ -61,6 +82,9 @@ class WebSocketHelper:
return False
return True
self.broadcast_with_fn(filter_fn, event_type, data)
def broadcast_with_fn(self, filter_fn, event_type: str, data):
clients = list(filter(filter_fn, self.clients))
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))

View File

@ -23,7 +23,7 @@
<div class="row">
<div class="col-md-3 grid-margin">
<div class="col-md-4 grid-margin">
<div class="card">
<div class="card-body">
@ -31,8 +31,8 @@
<div class="media-body">
<p class="card-text">
Patrons get access to several perks, such as behind the scenes videos, posts, and updates.
Patrons also get access to a Crafty Controller PE (Patreon Edition) with additional functions not in other versions of Crafty.
Patrons get access to several perks, such as behind the scenes videos, posts, and updates.<br>
Patrons also get early access to new software!
</p>
<br />
<div class="text-center">
@ -44,35 +44,7 @@
</div>
</div>
<div class="col-md-3 grid-margin">
<div class="card">
<div class="card-body">
<h4 class="card-title">One Time Support</h4>
<div class="media-body">
<p class="card-text">
Contribute via Paypal, you can contribute any amount, as often or as little as you want.
Please understand that while PayPal calls this a "Donation"; this is not a charitable donation and can not be claimed
as a charitable donation for tax purposes
</p>
<br />
<div class="text-center">
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_donations" />
<input type="hidden" name="business" value="H2HNTLFZAJRXG" />
<input type="hidden" name="currency_code" value="USD" />
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
</form>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 grid-margin">
<div class="col-md-4 grid-margin">
<div class="card">
<div class="card-body">
@ -93,6 +65,28 @@
</div>
</div>
<div class="col-md-4 grid-margin">
<div class="card">
<div class="card-body">
<h4 class="card-title">More Ways Coming Soon...</h4>
<div class="media-body">
<p class="card-text">
Thank you for your interest in contributing to Aracdia Technology's Crafty Controller.
We are always thinking of new ways for our community to contribute to this awesome project. <br><br> If you don't see
a contribution method that peaks your interest now please check back soon.
</p>
<br />
<div class="text-center">
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -366,7 +366,7 @@ dialog.init(function(){
}
if (webSocket) {
webSocket.on('send_start_reload', function (start_error) {
webSocket.on('send_start_reload', function () {
location.reload()
});
}

View File

@ -127,13 +127,17 @@
if (webSocket) {
webSocket.on('update_button_status', function (updateButton) {
if (updateButton.isUpdating){
if(updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
console.log(updateButton.isUpdating)
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data['lang']) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
}
}
else{
if (updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
window.location.reload()
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data['lang']) }}</button><button onclick="send_command(server_id, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
}
}
}
});
}
// Convert running to lower case (example: 'True' converts to 'true') and

View File

@ -15,14 +15,14 @@
<br />
<p class="card-description">
<form method="post" class="server-wizard">
<form method="post" class="server-wizard" onSubmit="wait_msg()">
{% raw xsrf_form_html() %}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="server_type">{{ translate('serverWizard', 'serverType', data['lang']) }}</label>
<select class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)">
<option value="empty">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
<select required class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)">
<option value="">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
{% for s in data['server_types'] %}
<option value="{{ s }}">{{ s.capitalize() }}</option>
{% end %}
@ -33,8 +33,8 @@
<div class="col-sm-12">
<div class="form-group">
<label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label>
<select class="form-control form-control-lg select-css" id="server" name="server">
<option value="0">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option>
<select required class="form-control form-control-lg select-css" id="server" name="server">
<option value="">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option>
</select>
</div>
</div>
@ -42,7 +42,7 @@
<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" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}">
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
@ -55,21 +55,21 @@
<div class="col-sm-3">
<div class="form-group">
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" min="0.5">
<input type="number" class="form-control" id="min_memory1" 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_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5" min="0.5">
<input type="number" class="form-control" id="max_memory1" 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="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1">
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1" required>
</div>
</div>
<div class="col-sm-12">
@ -97,7 +97,7 @@
</div>
</div>
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg()">{{ translate('serverWizard', 'buildServer', data['lang']) }}</button>
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'buildServer', data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
</form>
@ -114,7 +114,7 @@
<br />
<p class="card-description">
<form method="post" class="server-wizard">
<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">
@ -122,21 +122,21 @@
<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']) }}">
<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">
<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">
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
</div>
</div>
@ -151,21 +151,21 @@
<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">
<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">
<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">
<input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1" required>
</div>
</div>
<div class="col-sm-12">
@ -192,7 +192,7 @@
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
<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>
@ -209,7 +209,7 @@
<br />
<p class="card-description">
<form method="post" class="server-wizard">
<form method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
@ -218,21 +218,21 @@
<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']) }}">
<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">
<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_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar">
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
</div>
</div>
</div>
@ -247,21 +247,21 @@
<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">
<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">
<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">
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1" required>
</div>
</div>
@ -290,7 +290,7 @@
</div>
</div>
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
<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>
</div>
</div>