Merge branch 'lukas-bugfixes' into 'dev'

Bugfixes 🐛 and more

See merge request crafty-controller/crafty-commander!30
This commit is contained in:
computergeek125 2021-05-29 14:09:07 +00:00
commit 9dfc6ed449
21 changed files with 224 additions and 145 deletions

View File

@ -17,8 +17,8 @@ try:
import requests import requests
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e, e.name)) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e, e.name)) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)

View File

@ -16,7 +16,7 @@ try:
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
@ -29,10 +29,6 @@ class MainPrompt(cmd.Cmd, object):
# overrides the default Prompt # overrides the default Prompt
prompt = "Crafty Controller v{} > ".format(helper.get_version_string()) prompt = "Crafty Controller v{} > ".format(helper.get_version_string())
def __init__(self, tasks_manager):
super().__init__()
self.tasks_manager = tasks_manager
@staticmethod @staticmethod
def emptyline(): def emptyline():
pass pass
@ -49,6 +45,9 @@ class MainPrompt(cmd.Cmd, object):
console.critical("Unable to write exit file due to error: {}".format(e)) console.critical("Unable to write exit file due to error: {}".format(e))
def do_exit(self, line): def do_exit(self, line):
self.universal_exit()
def universal_exit(self):
logger.info("Stopping all server daemons / threads") logger.info("Stopping all server daemons / threads")
console.info("Stopping all server daemons / threads - This may take a few seconds") console.info("Stopping all server daemons / threads - This may take a few seconds")
websocket_helper.disconnect_all() websocket_helper.disconnect_all()

View File

@ -9,8 +9,8 @@ try:
from termcolor import colored from termcolor import colored
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logging.critical("Import Error: Unable to load {} module".format(e, e.name)) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
print("Import Error: Unable to load {} module".format(e, e.name)) print("Import Error: Unable to load {} module".format(e.name))
from app.classes.shared.installer import installer from app.classes.shared.installer import installer
installer.do_install() installer.do_install()
sys.exit(1) sys.exit(1)

View File

@ -28,7 +28,7 @@ try:
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
class Helpers: class Helpers:

View File

@ -18,8 +18,8 @@ try:
import yaml import yaml
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e, e.name)) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e, e.name)) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
schema_version = (0, 1, 0) # major, minor, patch semver schema_version = (0, 1, 0) # major, minor, patch semver
@ -503,6 +503,43 @@ class db_shortcuts:
except DoesNotExist: except DoesNotExist:
return None return None
@staticmethod
def get_user_by_api_token(token: str):
query = Users.select().where(Users.api_token == token)
if query.exists():
user = model_to_dict(Users.get(Users.api_token == token))
# I know it should apply it without setting it but I'm just making sure
user = db_shortcuts.add_user_roles(user)
return user
else:
return {}
@staticmethod
def add_user_roles(user):
if type(user) == dict:
user_id = user['user_id']
else:
user_id = user.user_id
# I just copied this code from get_user, it had those TODOs & comments made by mac - Lukas
roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id)
# TODO: this query needs to be narrower
roles = set()
for r in roles_query:
roles.add(r.role_id.role_id)
#servers_query = User_Servers.select().join(Servers, JOIN.INNER).where(User_Servers.user_id == user_id)
## TODO: this query needs to be narrower
servers = set()
#for s in servers_query:
# servers.add(s.server_id.server_id)
user['roles'] = roles
#user['servers'] = servers
#logger.debug("user: ({}) {}".format(user_id, user))
return user
@staticmethod @staticmethod
def get_user(user_id): def get_user(user_id):
if user_id == 0: if user_id == 0:
@ -523,19 +560,8 @@ class db_shortcuts:
user = model_to_dict(Users.get(Users.user_id == user_id)) user = model_to_dict(Users.get(Users.user_id == user_id))
if user: if user:
roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id) # I know it should apply it without setting it but I'm just making sure
# TODO: this query needs to be narrower user = db_shortcuts.add_user_roles(user)
roles = set()
for r in roles_query:
roles.add(r.role_id.role_id)
#servers_query = User_Servers.select().join(Servers, JOIN.INNER).where(User_Servers.user_id == user_id)
## TODO: this query needs to be narrower
servers = set()
#for s in servers_query:
# servers.add(s.server_id.server_id)
user['roles'] = roles
#user['servers'] = servers
#logger.debug("user: ({}) {}".format(user_id, user))
return user return user
else: else:
#logger.debug("user: ({}) {}".format(user_id, {})) #logger.debug("user: ({}) {}".format(user_id, {}))

View File

@ -23,8 +23,8 @@ try:
import pexpect import pexpect
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e, e.name)) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e, e.name)) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
@ -130,8 +130,7 @@ class Server:
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None) self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
self.is_crashed = False self.is_crashed = False
ts = time.time() self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
self.start_time = str(datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S'))
if psutil.pid_exists(self.process.pid): if psutil.pid_exists(self.process.pid):
self.PID = self.process.pid self.PID = self.process.pid

View File

@ -21,7 +21,7 @@ try:
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
scheduler_intervals = { 'seconds', scheduler_intervals = { 'seconds',

View File

@ -11,14 +11,21 @@ logger = logging.getLogger(__name__)
class Translation(): class Translation():
def __init__(self): def __init__(self):
self.translations_path = os.path.join(helper.root_dir, 'app', 'translations') self.translations_path = os.path.join(helper.root_dir, 'app', 'translations')
self.cached_translation = None
self.cached_translation_lang = None
def translate(self, page, word): def translate(self, page, word):
translated_word = None translated_word = None
lang = helper.get_setting('language') lang = helper.get_setting('language')
fallback_lang = 'en_EN' fallback_lang = 'en_EN'
translated_word = \ lang_file_exists = helper.check_file_exists(
self.translate_inner(page, word, lang) or \ os.path.join(
self.translate_inner(page, word, fallback_lang) self.translations_path, lang + '.json'
)
)
translated_word = self.translate_inner(page, word, lang) \
if lang_file_exists else self.translate_inner(page, word, fallback_lang)
if translated_word: if translated_word:
if isinstance(translated_word, dict): return json.dumps(translated_word) if isinstance(translated_word, dict): return json.dumps(translated_word)
@ -31,8 +38,17 @@ class Translation():
lang + '.json' lang + '.json'
) )
try: try:
if not self.cached_translation:
with open(lang_file, 'r') as f: with open(lang_file, 'r') as f:
data = json.load(f) data = json.load(f)
self.cached_translation = data
elif self.cached_translation_lang != lang:
with open(lang_file, 'r') as f:
data = json.load(f)
self.cached_translation = data
self.cached_translation_lang = lang
else:
data = self.cached_translation
try: try:
translated_page = data[page] translated_page = data[page]

View File

@ -6,53 +6,63 @@ import tornado.escape
import logging import logging
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
from app.classes.shared.models import Users from app.classes.shared.models import db_shortcuts
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ApiHandler(BaseHandler): class ApiHandler(BaseHandler):
def return_response(self, data: dict): def return_response(self, status: int, data: dict):
# Define a standardized response # Define a standardized response
self.set_status(status)
self.write(data) self.write(data)
def access_denied(self, user): def access_denied(self, user, reason=''):
log.info("User %s was denied access to API route", user) if reason: reason = ' because ' + reason
self.set_status(403) log.info("User %s from IP %s was denied access to the API route " + self.request.path + reason, user, self.get_remote_ip())
self.finish(self.return_response(403, {'error':'ACCESS_DENIED', 'info':'You were denied access to the requested resource'})) self.finish(self.return_response(403, {
'error':'ACCESS_DENIED',
'info':'You were denied access to the requested resource'
}))
def authenticate_user(self): def authenticate_user(self) -> bool:
try: try:
log.debug("Searching for specified token") log.debug("Searching for specified token")
# TODO: YEET THIS # TODO: YEET THIS
user_data = Users.get(api_token=self.get_argument('token')) user_data = db_shortcuts.get_user_by_api_token(self.get_argument('token'))
log.debug("Checking results") log.debug("Checking results")
if user_data: if user_data:
# Login successful! Check perms # Login successful! Check perms
log.info("User {} has authenticated to API".format(user_data.username)) log.info("User {} has authenticated to API".format(user_data['username']))
# TODO: Role check # TODO: Role check
return True # This is to set the "authenticated"
else: else:
logging.debug("Auth unsuccessful") logging.debug("Auth unsuccessful")
return self.access_denied("unknown") self.access_denied("unknown", "the user provided an invalid token")
except: return False
log.warning("Traceback occurred when authenticating user to API. Most likely wrong token") except Exception as e:
return self.access_denied("unknown") log.warning("An error occured while authenticating an API user: %s", e)
pass self.access_denied("unknown"), "an error occured while authenticating the user"
return False
class ServersStats(ApiHandler): class ServersStats(ApiHandler):
def get(self): def get(self):
"""Get details about all servers""" """Get details about all servers"""
self.authenticate_user() authenticated = self.authenticate_user()
if not authenticated: return
# Get server stats # Get server stats
# TODO Check perms
self.finish(self.write({"servers": self.controller.stats.get_servers_stats()})) self.finish(self.write({"servers": self.controller.stats.get_servers_stats()}))
class NodeStats(ApiHandler): class NodeStats(ApiHandler):
def get(self): def get(self):
"""Get stats for particular node""" """Get stats for particular node"""
self.authenticate_user() authenticated = self.authenticate_user()
if not authenticated: return
# Get node stats # Get node stats
node_stats = self.controller.stats.get_node_stats() node_stats = self.controller.stats.get_node_stats()
node_stats.pop("servers") node_stats.pop("servers")

View File

@ -61,7 +61,7 @@ class PanelHandler(BaseHandler):
} }
# if no servers defined, let's go to the build server area # if no servers defined, let's go to the build server area
if page_data['server_stats']['total'] == 0 and page != "error": if page_data['server_stats']['total'] == 0 and page != "error" and page != "credits" and page != "contribute":
self.set_status(301) self.set_status(301)
self.redirect("/server/step1") self.redirect("/server/step1")
return return

View File

@ -15,8 +15,8 @@ try:
import bleach import bleach
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e, e.name)) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e, e.name)) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
@ -81,13 +81,13 @@ class PublicHandler(BaseHandler):
# if we don't have a user # if we don't have a user
if not user_data: if not user_data:
next_page = "/public/error?error=Login_Failed" next_page = "/public/error?error=Login Failed"
self.redirect(next_page) self.redirect(next_page)
return False return False
# if they are disabled # if they are disabled
if not user_data.enabled: if not user_data.enabled:
next_page = "/public/error?error=Login_Failed" next_page = "/public/error?error=Login Failed"
self.redirect(next_page) self.redirect(next_page)
return False return False
@ -117,6 +117,10 @@ class PublicHandler(BaseHandler):
next_page = "/panel/dashboard" next_page = "/panel/dashboard"
self.redirect(next_page) self.redirect(next_page)
else:
# log this failed login attempt
db_helper.add_to_audit_log(user_data.user_id, "Tried to log in", 0, self.get_remote_ip())
self.redirect('/public/error?error=Login Failed')
else: else:
self.redirect("/public/login") self.redirect("/public/login")

View File

@ -19,8 +19,8 @@ try:
import bleach import bleach
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e, e.name)) logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
console.critical("Import Error: Unable to load {} module".format(e, e.name)) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
@ -163,6 +163,10 @@ class ServerHandler(BaseHandler):
import_server_jar = bleach.clean(self.get_argument('server_jar', '')) import_server_jar = bleach.clean(self.get_argument('server_jar', ''))
server_parts = server.split("|") server_parts = server.split("|")
if not server_name:
self.redirect("/panel/error?error=Server name cannot be empty!")
return False
if import_type == 'import_jar': if import_type == 'import_jar':
good_path = self.controller.verify_jar_server(import_server_path, import_server_jar) good_path = self.controller.verify_jar_server(import_server_path, import_server_jar)
@ -171,7 +175,12 @@ class ServerHandler(BaseHandler):
return False return False
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port) new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
db_helper.add_to_audit_log(exec_user_data['user_id'],
"imported a jar server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
elif import_type == 'import_zip': elif import_type == 'import_zip':
# here import_server_path means the zip path
good_path = self.controller.verify_zip_server(import_server_path) good_path = self.controller.verify_zip_server(import_server_path)
if not good_path: if not good_path:
self.redirect("/panel/error?error=Zip file not found!") self.redirect("/panel/error?error=Zip file not found!")
@ -179,28 +188,24 @@ class ServerHandler(BaseHandler):
new_server_id = self.controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port) new_server_id = self.controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
if new_server_id == "false": if new_server_id == "false":
self.redirect("/panel/error?error=ZIP file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path)) self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
return False return False
db_helper.add_to_audit_log(exec_user_data['user_id'],
"imported a zip server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
else: else:
if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data")
return False
server_type, server_version = server_parts
# todo: add server type check here and call the correct server add functions if not a jar # todo: add server type check here and call the correct server add functions if not a jar
new_server_id = self.controller.create_jar_server(server_parts[0], server_parts[1], server_name, min_mem, max_mem, port) new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
if new_server_id is not None and exec_user_data is not None and len(server_parts) > 1:
db_helper.add_to_audit_log(exec_user_data['user_id'], db_helper.add_to_audit_log(exec_user_data['user_id'],
"created a {} {} server named \"{}\"".format(server_parts[1], str(server_parts[0]).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival" "created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id, new_server_id,
self.get_remote_ip()) self.get_remote_ip())
elif new_server_id is not None and exec_user_data is not None:
db_helper.add_to_audit_log(exec_user_data['user_id'],
"created a {} {} server named \"{}\"".format("Minecraft", str(server_parts[0]).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip())
else:
logger.error("Unable to create server")
console.error("Unable to create server")
self.controller.stats.record_stats() self.controller.stats.record_stats()
self.redirect("/panel/dashboard") self.redirect("/panel/dashboard")

View File

@ -0,0 +1,15 @@
import tornado.web
from typing import (
Optional
)
from app.classes.shared.console import console
class CustomStaticHandler(tornado.web.StaticFileHandler):
def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
try:
return super().validate_absolute_path(root, absolute_path)
except tornado.web.HTTPError as error:
if 'HTTP 404: Not Found' in str(error):
self.set_status(404)
self.finish({'error':'NOT_FOUND', 'info':'The requested resource was not found on the server'})

View File

@ -25,6 +25,7 @@ try:
from app.classes.web.ajax_handler import AjaxHandler from app.classes.web.ajax_handler import AjaxHandler
from app.classes.web.api_handler import ServersStats, NodeStats from app.classes.web.api_handler import ServersStats, NodeStats
from app.classes.web.websocket_handler import SocketHandler from app.classes.web.websocket_handler import SocketHandler
from app.classes.web.static_handler import CustomStaticHandler
from app.classes.shared.translation import translation from app.classes.shared.translation import translation
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
@ -112,9 +113,6 @@ class Webserver:
logger.info("Starting Web Server on ports http:{} https:{}".format(http_port, https_port)) logger.info("Starting Web Server on ports http:{} https:{}".format(http_port, https_port))
console.info("http://{}:{} is up and ready for connection:".format(helper.get_local_ip(), http_port))
console.info("https://{}:{} is up and ready for connection:".format(helper.get_local_ip(), https_port))
asyncio.set_event_loop(asyncio.new_event_loop()) asyncio.set_event_loop(asyncio.new_event_loop())
tornado.template.Loader('.') tornado.template.Loader('.')
@ -143,7 +141,9 @@ class Webserver:
autoreload=False, autoreload=False,
log_function=self.log_function, log_function=self.log_function,
login_url="/login", login_url="/login",
default_handler_class=PublicHandler default_handler_class=PublicHandler,
static_handler_class=CustomStaticHandler,
serve_traceback=debug_errors,
) )
self.HTTP_Server = tornado.httpserver.HTTPServer(app) self.HTTP_Server = tornado.httpserver.HTTPServer(app)
@ -152,6 +152,11 @@ class Webserver:
self.HTTPS_Server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects) self.HTTPS_Server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects)
self.HTTPS_Server.listen(https_port) self.HTTPS_Server.listen(https_port)
logger.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
logger.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
console.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
console.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
console.info("Server Init Complete: Listening For Connections:") console.info("Server Init Complete: Listening For Connections:")
self.ioloop = tornado.ioloop.IOLoop.instance() self.ioloop = tornado.ioloop.IOLoop.instance()

View File

@ -43,10 +43,12 @@
<a class="nav-link active" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true"> <a class="nav-link active" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a> <i class="fas fa-cogs"></i>Config</a>
</li> </li>
{% if data['new_user'] %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false"> <a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a> <i class="fas fa-folder-tree"></i>Other</a>
</li> </li>
{% end %}
</ul> </ul>
<div class="row"> <div class="row">

View File

@ -86,11 +86,8 @@
let startedLocal; let startedLocal;
if (started != null) { if (started != null) {
console.log('88', '{{ data['server_stats']['started'] }}'); startedUTC = '{{ data['server_stats']['started'] }}';
{% if data['server_stats']['started'] != 'False' %} console.log('started utc:', startedUTC);
startedUTC = '{{ (datetime.datetime.strptime(data['server_stats']['started'], '%Y-%m-%d %H:%M:%S') - datetime.timedelta(seconds=-time.timezone)).strftime('%Y-%m-%d %H:%M:%S') }}';
{% end %}
console.log('utc', startedUTC);
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss'); startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
let browserUTCOffset = moment().utcOffset(); // This is in minutes let browserUTCOffset = moment().utcOffset(); // This is in minutes
@ -98,32 +95,25 @@
startedLocal = startedUTC.utcOffset(browserUTCOffset); startedLocal = startedUTC.utcOffset(browserUTCOffset);
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss'); startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
console.log('startedLocal', startedLocal); console.log('started local time:', startedLocalFormatted);
console.log('startedLocalFormatted', startedLocalFormatted);
started.textContent = startedLocalFormatted started.textContent = startedLocalFormatted
} }
let nowServerTime = '{{ data['time'] }}'; var calculateUptime = () => {
let startedServerTime = '{{ data['server_stats']['started'] }}';
if (uptime != null && started != null) {
var msdiff = moment(nowServerTime,"YYYY-MM-DD hh:mm:ss")
.diff(moment(startedServerTime,"YYYY-MM-DD hh:mm:ss"));
var diff = moment.duration(msdiff);
uptime.textContent = durationToHumanizedString(diff);
console.log('startedLocal', startedLocal)
if (startedLocal) {
var uptimeLoop = setInterval(() => {
var msdiff = moment() var msdiff = moment()
.diff(startedLocal); .diff(startedLocal);
var diff = moment.duration(msdiff); var diff = moment.duration(msdiff);
uptime.textContent = durationToHumanizedString(diff); uptime.textContent = durationToHumanizedString(diff);
}, 1000) }
if (uptime != null && started != null) {
console.log('startedLocal', startedLocal)
if (startedLocal) {
calculateUptime()
var uptimeLoop = setInterval(calculateUptime, 1000)
} }
} }

View File

@ -194,6 +194,7 @@
} }
</style> </style>
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<h2 id="fileError"></h2>
<div id="editorParent"> <div id="editorParent">
{{ translate('serverFiles', 'editingFile') }} <span id="editingFile"></span> {{ translate('serverFiles', 'editingFile') }} <span id="editingFile"></span>
<div id="editor" onresize="editor.resize()" style="resize: both;width: 100%;">file_contents</div> <div id="editor" onresize="editor.resize()" style="resize: both;width: 100%;">file_contents</div>
@ -344,10 +345,13 @@
console.log('Got File Contents From Server'); console.log('Got File Contents From Server');
json = JSON.parse(data) json = JSON.parse(data)
if (json.error) { if (json.error) {
$('#editorParent').toggle(false) $('#editorParent').toggle(false) // hide
$('#fileError').toggle(true) // show
$('#fileError').text("{{ translate('serverFiles', 'fileReadError') }}: " + json.error) // show error
editor.blur() editor.blur()
} else { } else {
$('#editorParent').toggle(true) $('#editorParent').toggle(true) // show
$('#fileError').toggle(false) // hide
setFileName(event.target.innerText); setFileName(event.target.innerText);
editor.session.setValue(json.content); editor.session.setValue(json.content);
} }
@ -376,7 +380,8 @@
} }
setFileName(); setFileName();
$('#editorParent').toggle(false) $('#editorParent').toggle(false) // show
$('#fileError').toggle(false) // hide
editor.blur() editor.blur()
function setMode (extension) { function setMode (extension) {

View File

@ -14,7 +14,7 @@
<br /> <br />
<p class="card-description"> <p class="card-description">
<form method="post"> <form method="post" class="server-wizard">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
@ -47,22 +47,22 @@
<div class="col-sm-3"> <div class="col-sm-3">
<div class="form-group"> <div class="form-group">
<label for="min_memory">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label> <label for="min_memory1">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
<input type="number" class="form-control" id="min_memory" 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">
</div> </div>
</div> </div>
<div class="col-sm-3 offset-1"> <div class="col-sm-3 offset-1">
<div class="form-group"> <div class="form-group">
<label for="max_memory">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label> <label for="max_memory1">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
<input type="number" class="form-control" id="max_memory" 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">
</div> </div>
</div> </div>
<div class="col-sm-3 offset-1"> <div class="col-sm-3 offset-1">
<div class="form-group"> <div class="form-group">
<label for="port">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label> <label for="port1">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label>
<input type="number" class="form-control" id="port" name="port" value="25565" step="1" min="1"> <input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1">
</div> </div>
</div> </div>
@ -84,7 +84,7 @@
<br /> <br />
<p class="card-description"> <p class="card-description">
<form method="post"> <form method="post" class="server-wizard">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" value="import_jar" name="create_type"> <input type="hidden" value="import_jar" name="create_type">
<div class="row"> <div class="row">
@ -120,22 +120,22 @@
<div class="col-sm-3"> <div class="col-sm-3">
<div class="form-group"> <div class="form-group">
<label for="min_memory">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label> <label for="min_memory2">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
<input type="number" class="form-control" id="min_memory" 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">
</div> </div>
</div> </div>
<div class="col-sm-3 offset-1"> <div class="col-sm-3 offset-1">
<div class="form-group"> <div class="form-group">
<label for="max_memory">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label> <label for="max_memory2">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
<input type="number" class="form-control" id="max_memory" 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">
</div> </div>
</div> </div>
<div class="col-sm-3 offset-1"> <div class="col-sm-3 offset-1">
<div class="form-group"> <div class="form-group">
<label for="port">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label> <label for="port2">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label>
<input type="number" class="form-control" id="port" name="port" value="25565" step="1" min="1"> <input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1">
</div> </div>
</div> </div>
@ -157,7 +157,7 @@
<br /> <br />
<p class="card-description"> <p class="card-description">
<form method="post"> <form method="post" class="server-wizard">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type"> <input type="hidden" value="import_zip" name="create_type">
@ -194,22 +194,22 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="min_memory">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label> <label for="min_memory3">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
<input type="number" class="form-control" id="min_memory" 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">
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="max_memory">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label> <label for="max_memory3">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
<input type="number" class="form-control" id="max_memory" 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">
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="port">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label> <label for="port3">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label>
<input type="number" class="form-control" id="port" name="port" value="25565" step="1" min="1"> <input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1">
</div> </div>
</div> </div>
@ -232,11 +232,18 @@
<script> <script>
$( document ).ready(function() { $( document ).ready(function() {
console.log('ready'); console.log('ready');
$("#min_memory").change(function(){ var forms = $('form.server-wizard');
check_sizes('min'); 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');
}); });
$("#max_memory").change(function(){
check_sizes('max');
}); });
}); });
@ -247,14 +254,14 @@
}); });
} }
function check_sizes(changed){ function check_sizes(a, b, changed){
max_mem = parseFloat($('#max_memory').val()); max_mem = parseFloat(a.val());
min_mem = parseFloat($('#min_memory').val()); min_mem = parseFloat(b.val());
if (max_mem < min_mem && changed === 'min'){ if (max_mem < min_mem && changed === 'min'){
$('#max_memory').val(min_mem) a.val(min_mem)
} }
if (max_mem < min_mem && changed === 'max'){ if (max_mem < min_mem && changed === 'max'){
$('#min_memory').val(max_mem) b.val(max_mem)
} }
} }

View File

@ -106,7 +106,7 @@
"version": "Version", "version": "Version",
"description": "Description", "description": "Description",
"errorCalculatingUptime": "Error Calculating Uptime", "errorCalculatingUptime": "Error Calculating Uptime",
"serverTime": "Server Time", "serverTime": "UTC Time",
"unableToConnect": "Unable To Connect" "unableToConnect": "Unable To Connect"
}, },
"serverDetails": { "serverDetails": {
@ -170,7 +170,8 @@
"yesDelete": "Yes, I understand the consequences", "yesDelete": "Yes, I understand the consequences",
"noDelete": "No", "noDelete": "No",
"unsupportedLanguage": "Warning: This is not a supported file type", "unsupportedLanguage": "Warning: This is not a supported file type",
"keybindings": "Keybindings" "keybindings": "Keybindings",
"fileReadError": "File read error"
}, },
"serverConfig": { "serverConfig": {
"serverName": "Server Name", "serverName": "Server Name",

View File

@ -106,7 +106,7 @@
"version": "Versio", "version": "Versio",
"description": "Kuvaus", "description": "Kuvaus",
"errorCalculatingUptime": "Virhe laskettaessa käyttöaikaa", "errorCalculatingUptime": "Virhe laskettaessa käyttöaikaa",
"serverTime": "Palvelimen aikaa", "serverTime": "UTC aikaa",
"unableToConnect": "Yhteyden muodostaminen epäonnistui" "unableToConnect": "Yhteyden muodostaminen epäonnistui"
}, },
"serverDetails": { "serverDetails": {
@ -166,11 +166,12 @@
"createDirQuestion": "Minkä nimen haluat uudelle hakemistolle?", "createDirQuestion": "Minkä nimen haluat uudelle hakemistolle?",
"renameItemQuestion": "Mikä uuden nimen pitäisi olla?", "renameItemQuestion": "Mikä uuden nimen pitäisi olla?",
"deleteItemQuestion": "Haluatko varmasti poistaa \" + name + \"?", "deleteItemQuestion": "Haluatko varmasti poistaa \" + name + \"?",
"deleteItemQuestionMessage": "Olet poistamassa \\\"\" + path + \"\\\"!<br/><br/>Tämä toiminta on peruuttamaton ja se menetetään ikuisesti!", "deleteItemQuestionMessage": "Olet poistamassa \\\"\" + path + \"\\\"!<br/><br/>Tämä toiminto on peruuttamaton ja se menetetään ikuisesti!",
"yesDelete": "Kyllä, ymmärrän seuraukset", "yesDelete": "Kyllä, ymmärrän seuraukset",
"noDelete": "En", "noDelete": "En",
"unsupportedLanguage": "Varoitus: Tätä tiedostotyyppiä ei tueta", "unsupportedLanguage": "Varoitus: Tätä tiedostotyyppiä ei tueta",
"keybindings": "Pikanäppäimet" "keybindings": "Pikanäppäimet",
"fileReadError": "Tiedoston lukuvirhe"
}, },
"serverConfig": { "serverConfig": {
"serverName": "Palvelimen nimi", "serverName": "Palvelimen nimi",

View File

@ -141,10 +141,4 @@ if __name__ == '__main__':
logger.info("Recieved SIGINT, stopping Crafty") logger.info("Recieved SIGINT, stopping Crafty")
break break
logger.info("Stopping all server daemons / threads") Crafty.universal_exit()
console.info("Stopping all server daemons / threads - This may take a few seconds")
Crafty._clean_shutdown()
while True:
if tasks_manager.get_main_thread_run_status():
sys.exit(0)
time.sleep(1)