Improved File Loading, Fixed Port checking

This commit is contained in:
Andrew 2022-01-08 23:03:45 +00:00
parent abb7b8af08
commit 7dd24b6b15
21 changed files with 307 additions and 89 deletions

View File

@ -94,8 +94,8 @@ class Users_Controller:
users_helper.update_user(user_id, up_data) users_helper.update_user(user_id, up_data)
@staticmethod @staticmethod
def add_user(username, password=None, api_token=None, enabled=True, superuser=False): def add_user(username, password=None, email="default@example.com", api_token=None, enabled=True, superuser=False):
return users_helper.add_user(username, password=password, api_token=api_token, enabled=enabled, superuser=superuser) return users_helper.add_user(username, password=password, email=email, api_token=api_token, enabled=enabled, superuser=superuser)
@staticmethod @staticmethod
def remove_user(user_id): def remove_user(user_id):

View File

@ -38,6 +38,7 @@ class Users(Model):
last_ip = CharField(default="") last_ip = CharField(default="")
username = CharField(default="", unique=True, index=True) username = CharField(default="", unique=True, index=True)
password = CharField(default="") password = CharField(default="")
email = CharField(default="default@example.com")
enabled = BooleanField(default=True) enabled = BooleanField(default=True)
superuser = BooleanField(default=False) superuser = BooleanField(default=False)
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
@ -112,6 +113,7 @@ class helper_users:
'last_ip': "127.27.23.89", 'last_ip': "127.27.23.89",
'username': "SYSTEM", 'username': "SYSTEM",
'password': None, 'password': None,
'email': "default@example.com",
'enabled': True, 'enabled': True,
'superuser': True, 'superuser': True,
'api_token': None, 'api_token': None,
@ -136,7 +138,7 @@ class helper_users:
return False return False
@staticmethod @staticmethod
def add_user(username, password=None, api_token=None, enabled=True, superuser=False): def add_user(username, password=None, email=None, api_token=None, enabled=True, superuser=False):
if password is not None: if password is not None:
pw_enc = helper.encode_pass(password) pw_enc = helper.encode_pass(password)
else: else:
@ -149,6 +151,7 @@ class helper_users:
user_id = Users.insert({ user_id = Users.insert({
Users.username: username.lower(), Users.username: username.lower(),
Users.password: pw_enc, Users.password: pw_enc,
Users.email: email,
Users.api_token: api_token, Users.api_token: api_token,
Users.enabled: enabled, Users.enabled: enabled,
Users.superuser: superuser, Users.superuser: superuser,

View File

@ -97,13 +97,33 @@ class Helpers:
@staticmethod @staticmethod
def check_port(server_port): def check_port(server_port):
try:
ip = get('https://api.ipify.org').content.decode('utf8')
except:
ip = 'google.com'
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
a_socket.settimeout(20.0)
ip = get('https://api.ipify.org').content.decode('utf8')
location = (ip, server_port) location = (ip, server_port)
result_of_check = a_socket.connect_ex(location) result_of_check = a_socket.connect_ex(location)
a_socket.close()
if result_of_check == 0:
return True
else:
return False
@staticmethod
def check_server_conn(server_port):
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
a_socket.settimeout(10.0)
ip = '127.0.0.1'
location = (ip, server_port)
result_of_check = a_socket.connect_ex(location)
a_socket.close()
if result_of_check == 0: if result_of_check == 0:
return True return True
else: else:
@ -654,29 +674,58 @@ class Helpers:
@staticmethod @staticmethod
def generate_tree(folder, output=""): def generate_tree(folder, output=""):
file_list = os.listdir(folder) file_list = os.listdir(folder)
file_list.sort() file_list = sorted(file_list, key=str.casefold)
for raw_filename in file_list: for raw_filename in file_list:
filename = html.escape(raw_filename) filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename) rel = os.path.join(folder, raw_filename)
if os.path.isdir(rel): if os.path.isdir(rel):
output += \ output += \
"""<li class="tree-item" data-path="{}"> """<li class="tree-item" data-path="{}">
\n<div data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder"> \n<div id="{}" data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
<span id="{}span" class="files-tree-title" data-path="{}" onclick="getDirView(event)">
<i class="far fa-folder"></i> <i class="far fa-folder"></i>
<i class="far fa-folder-open"></i> <i class="far fa-folder-open"></i>
{} {}
</div> </span>
\n<ul class="tree-nested">"""\ </div><li>
.format(os.path.join(folder, filename), os.path.join(folder, filename), filename, filename) \n"""\
.format(os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, os.path.join(folder, filename), os.path.join(folder, filename), filename)
else:
output += """<li
class="tree-item tree-ctx-item tree-file"
data-path="{}"
data-name="{}"
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{}</li>""".format(os.path.join(folder, filename), filename, filename)
return output
output += helper.generate_tree(rel) @staticmethod
output += '</ul>\n</li>' def generate_dir(folder, output=""):
file_list = os.listdir(folder)
file_list = sorted(file_list, key=str.casefold)
output += \
"""<ul class="tree-nested d-block" id="{}ul">"""\
.format(folder)
for raw_filename in file_list:
filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename)
if os.path.isdir(rel):
output += \
"""<li class="tree-item" data-path="{}">
\n<div id="{}" data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
<span id="{}span" class="files-tree-title" data-path="{}" onclick="getDirView(event)">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{}
</span>
</div><li>"""\
.format(os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, os.path.join(folder, filename), os.path.join(folder, filename), filename)
else: else:
output += """<li output += """<li
class="tree-item tree-ctx-item tree-file" class="tree-item tree-ctx-item tree-file"
data-path="{}" data-path="{}"
data-name="{}" data-name="{}"
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{}</li>""".format(os.path.join(folder, filename), filename, filename) onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{}</li>""".format(os.path.join(folder, filename), filename, filename)
output += '</ul>\n'
return output return output
@staticmethod @staticmethod

View File

@ -130,7 +130,7 @@ class Controller:
@staticmethod @staticmethod
def add_system_user(): def add_system_user():
helper_users.add_user("system", helper.random_string_generator(64), helper_users.new_api_token(), False, False) helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", helper_users.new_api_token(), False, False)
def get_server_settings(self, server_id): def get_server_settings(self, server_id):
for s in self.servers_list: for s in self.servers_list:

View File

@ -48,7 +48,7 @@ class db_builder:
# Users.enabled: True, # Users.enabled: True,
# Users.superuser: True # Users.superuser: True
#}).execute() #}).execute()
user_id = users_helper.add_user(username=username, password=password, superuser=True) user_id = users_helper.add_user(username=username, password=password, email="default@example.com", superuser=True)
#users_helper.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} ) #users_helper.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} )
#console.info("API token is {}".format(api_token)) #console.info("API token is {}".format(api_token))

View File

@ -274,6 +274,8 @@ class Server:
self.stats.record_stats() self.stats.record_stats()
websocket_helper.broadcast_user(user_id, 'send_start_reload', { websocket_helper.broadcast_user(user_id, 'send_start_reload', {
}) })
check_port_thread = threading.Thread(target=self.check_internet_thread, daemon=True, args=(user_id, user_lang, ), name=f"backup_{self.name}")
check_port_thread.start()
else: else:
logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid)) logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid))
console.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid)) console.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid))
@ -284,27 +286,26 @@ class Server:
self.crash_watcher_schedule = schedule.every(30).seconds.do(self.detect_crash).tag(self.name) self.crash_watcher_schedule = schedule.every(30).seconds.do(self.detect_crash).tag(self.name)
check_port_thread = threading.Thread(target=self.check_internet_thread, daemon=True, args=(user_id, user_lang, ), name=f"backup_{self.name}")
check_port_thread.start()
def check_internet_thread(self, user_id, user_lang): def check_internet_thread(self, user_id, user_lang):
if user_id: if user_id:
if helper.check_internet(): if helper.check_internet():
loc_server_port = servers_helper.get_server_stats_by_id(self.server_id)['server_port'] loc_server_port = servers_helper.get_server_stats_by_id(self.server_id)['server_port']
port_status = False port_status = False
for i in range(3): checked = False
if helper.check_port(loc_server_port): while not checked:
port_status = True if helper.check_server_conn(loc_server_port):
return checked = True
result_of_check = helper.check_port(loc_server_port)
if result_of_check == True:
return
else:
websocket_helper.broadcast_user(user_id, 'send_start_error', {
'error': translation.translate('error', 'closedPort', user_lang).format(loc_server_port)
})
else: else:
time.sleep(5) time.sleep(5)
if port_status == False:
websocket_helper.broadcast_user(user_id, 'send_start_error', {
'error': translation.translate('error', 'closedPort', user_lang).format(loc_server_port)
})
else: else:
websocket_helper.broadcast_user(user_id, 'send_start_error', { websocket_helper.broadcast_user(user_id, 'send_start_error', {
'error': translation.translate('error', 'internet', user_lang) 'error': translation.translate('error', 'internet', user_lang)

View File

@ -124,12 +124,26 @@ class AjaxHandler(BaseHandler):
elif page == "get_tree": elif page == "get_tree":
server_id = self.get_argument('id', None) server_id = self.get_argument('id', None)
path = self.get_argument('path', None)
if not self.check_server_id(server_id, 'get_tree'): return if not self.check_server_id(server_id, 'get_tree'): return
else: server_id = bleach.clean(server_id) else: server_id = bleach.clean(server_id)
self.write(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']) + '\n' + if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
helper.generate_tree(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']))) self.write(helper.get_os_understandable_path(path) + '\n' +
helper.generate_tree(path))
self.finish()
elif page == "get_dir":
server_id = self.get_argument('id', None)
path = self.get_argument('path', None)
if not self.check_server_id(server_id, 'get_tree'): return
else: server_id = bleach.clean(server_id)
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
self.write(helper.get_os_understandable_path(path) + '\n' +
helper.generate_dir(path))
self.finish() self.finish()
@tornado.web.authenticated @tornado.web.authenticated
@ -328,7 +342,8 @@ class AjaxHandler(BaseHandler):
return return
# Delete the file # Delete the file
os.remove(file_path) if helper.validate_traversal(helper.get_os_understandable_path(server_info['path']), file_path):
os.remove(file_path)
elif page == "del_dir": elif page == "del_dir":
if not permissions['Files'] in user_perms: if not permissions['Files'] in user_perms:
@ -352,7 +367,8 @@ class AjaxHandler(BaseHandler):
# Delete the directory # Delete the directory
# os.rmdir(dir_path) # Would only remove empty directories # os.rmdir(dir_path) # Would only remove empty directories
shutil.rmtree(dir_path) # Removes also when there are contents if helper.validate_traversal(helper.get_os_understandable_path(server_info['path']), dir_path):
shutil.rmtree(dir_path) # Removes also when there are contents
elif page == "delete_server": elif page == "delete_server":
if not permissions['Config'] in user_perms: if not permissions['Config'] in user_perms:

View File

@ -422,6 +422,7 @@ class PanelHandler(BaseHandler):
page_data['user'] = {} page_data['user'] = {}
page_data['user']['username'] = "" page_data['user']['username'] = ""
page_data['user']['user_id'] = -1 page_data['user']['user_id'] = -1
page_data['user']['email'] = ""
page_data['user']['enabled'] = True page_data['user']['enabled'] = True
page_data['user']['superuser'] = False page_data['user']['superuser'] = False
page_data['user']['api_token'] = "N/A" page_data['user']['api_token'] = "N/A"
@ -489,6 +490,9 @@ class PanelHandler(BaseHandler):
if exec_user['user_id'] != page_data['user']['user_id']: if exec_user['user_id'] != page_data['user']['user_id']:
page_data['user']['api_token'] = "********" page_data['user']['api_token'] = "********"
if exec_user['email'] == 'default@example.com':
page_data['user']['email'] = ""
template = "panel/panel_edit_user.html" template = "panel/panel_edit_user.html"
elif page == "remove_user": elif page == "remove_user":
@ -824,6 +828,7 @@ class PanelHandler(BaseHandler):
username = bleach.clean(self.get_argument('username', None)) username = bleach.clean(self.get_argument('username', None))
password0 = bleach.clean(self.get_argument('password0', None)) password0 = bleach.clean(self.get_argument('password0', None))
password1 = bleach.clean(self.get_argument('password1', None)) password1 = bleach.clean(self.get_argument('password1', None))
email = bleach.clean(self.get_argument('email', "default@example.com"))
enabled = int(float(self.get_argument('enabled', '0'))) enabled = int(float(self.get_argument('enabled', '0')))
regen_api = int(float(self.get_argument('regen_api', '0'))) regen_api = int(float(self.get_argument('regen_api', '0')))
lang = bleach.clean(self.get_argument('language'), 'en_EN') lang = bleach.clean(self.get_argument('language'), 'en_EN')
@ -894,9 +899,13 @@ class PanelHandler(BaseHandler):
else: else:
server_quantity[permission.name] = 0 server_quantity[permission.name] = 0
# if email is None or "":
# email = "default@example.com"
user_data = { user_data = {
"username": username, "username": username,
"password": password0, "password": password0,
"email": email,
"enabled": enabled, "enabled": enabled,
"regen_api": regen_api, "regen_api": regen_api,
"roles": roles, "roles": roles,
@ -922,6 +931,7 @@ class PanelHandler(BaseHandler):
username = bleach.clean(self.get_argument('username', None)) username = bleach.clean(self.get_argument('username', None))
password0 = bleach.clean(self.get_argument('password0', None)) password0 = bleach.clean(self.get_argument('password0', None))
password1 = bleach.clean(self.get_argument('password1', None)) password1 = bleach.clean(self.get_argument('password1', None))
email = bleach.clean(self.get_argument('email', "default@example.com"))
enabled = int(float(self.get_argument('enabled', '0'))), enabled = int(float(self.get_argument('enabled', '0'))),
lang = bleach.clean(self.get_argument('lang', 'en_EN')) lang = bleach.clean(self.get_argument('lang', 'en_EN'))
@ -972,7 +982,7 @@ class PanelHandler(BaseHandler):
else: else:
server_quantity[permission.name] = 0 server_quantity[permission.name] = 0
user_id = self.controller.users.add_user(username, password=password0, enabled=enabled) user_id = self.controller.users.add_user(username, password=password0, email=email, enabled=enabled)
user_data = { user_data = {
"roles": roles, "roles": roles,
'lang': lang 'lang': lang

View File

@ -1,10 +1,13 @@
from re import X
import sys import sys
import json import json
import libgravatar
import logging import logging
import requests
import tornado.web import tornado.web
import tornado.escape import tornado.escape
from app.classes.shared.helpers import helper from app.classes.shared.helpers import Helpers, helper
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
from app.classes.shared.console import console from app.classes.shared.console import console
from app.classes.shared.main_models import fn from app.classes.shared.main_models import fn
@ -121,9 +124,27 @@ class PublicHandler(BaseHandler):
# log this login # log this login
self.controller.management.add_to_audit_log(user_data.user_id, "Logged in", 0, self.get_remote_ip()) self.controller.management.add_to_audit_log(user_data.user_id, "Logged in", 0, self.get_remote_ip())
if helper.get_setting("allow_nsfw_profile_pictures"):
rating = "x"
else:
rating = "g"
#Get grvatar hash for profile pictures
if user_data.email != 'default@example.com' or "":
g = libgravatar.Gravatar(libgravatar.sanitize_email(user_data.email))
url = g.get_image(size=80, default="404", force_default=False, rating=rating, filetype_extension=False, use_ssl=True) # + "?d=404"
if requests.head(url).status_code != 404:
profile_url = url
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
cookie_data = { cookie_data = {
"username": user_data.username, "username": user_data.username,
"user_id": user_data.user_id, "user_id": user_data.user_id,
"email": user_data.email,
"profile_url": profile_url,
"account_type": user_data.superuser, "account_type": user_data.superuser,
} }

View File

@ -12,5 +12,6 @@
"show_contribute_link": true, "show_contribute_link": true,
"virtual_terminal_lines": 70, "virtual_terminal_lines": 70,
"max_log_lines": 700, "max_log_lines": 700,
"keywords": ["help", "chunk"] "keywords": ["help", "chunk"],
"allow_nsfw_profile_pictures": false
} }

View File

@ -29,6 +29,7 @@
<link rel="shortcut icon" href="/static/assets/images/favicon.png" /> <link rel="shortcut icon" href="/static/assets/images/favicon.png" />
<link rel="stylesheet" href="/static/assets/css/crafty.css"> <link rel="stylesheet" href="/static/assets/css/crafty.css">
</head> </head>
<body class="dark-theme"> <body class="dark-theme">

View File

@ -2,7 +2,11 @@
<footer class="footer"> <footer class="footer">
<div class="container-fluid "> <div class="container-fluid ">
<<<<<<< app/frontend/templates/footer.html
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 - 2022 <a href="https://craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
=======
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 - 2022 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span> <span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 - 2022 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
>>>>>>> app/frontend/templates/footer.html
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }} <span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}
</span> </span>
</div> </div>

View File

@ -18,15 +18,16 @@
<li class="nav-item dropdown user-dropdown"> <li class="nav-item dropdown user-dropdown">
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false"> <a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
<img class="img-xs rounded-circle" src="/static/assets/images/faces-clipart/pic-1.png" alt="Profile image"> </a> <img class="img-xs rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image"> </a>
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown"> <div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
<div class="dropdown-header text-center"> <div class="dropdown-header text-center">
<img class="img-md rounded-circle" src="/static/assets/images/faces-clipart/pic-1.png" alt="Profile image"> <img class="img-md rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image">
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p> <p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
<p class="font-weight-light text-muted mb-0">Roles: </p> <p class="font-weight-light text-muted mb-0">Roles: </p>
{% for r in data['user_role'] %} {% for r in data['user_role'] %}
<p class="font-weight-light text-muted mb-0">{{ r }}</p> <p class="font-weight-light text-muted mb-0">{{ r }}</p>
{% end %} {% end %}
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
</div> </div>
{% if "Super User" in data['user_role'] %} {% if "Super User" in data['user_role'] %}
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i> Activity</a> <a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i> Activity</a>

View File

@ -80,6 +80,10 @@
<label class="form-label" for="password1">Repeat Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label> <label class="form-label" for="password1">Repeat Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" > <input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
</div> </div>
<div class="form-group">
<label class="form-label" for="email">Gravatar Email <small class="text-muted ml-1"> - for the profile picture. this is not required. crafty will never make use of user emails. User emails are strictly for Gravatar</small> </label>
<input type="email" class="form-control" name="email" id="email" value="{{ data['user']['email'] }}" placeholder="Gravatar Email" >
</div>
<div class="form-group"> <div class="form-group">
<label class="form-label" for="language">User Language:</label> <label class="form-label" for="language">User Language:</label>
<select class="form-select form-control form-control-lg select-css" id="language" name="language" form="user_form"> <select class="form-select form-control form-control-lg select-css" id="language" name="language" form="user_form">

View File

@ -25,7 +25,7 @@
</div> </div>
<!-- Page Title Header Ends--> <!-- Page Title Header Ends-->
{% include "parts/details_stats.html %} {% include "parts/details_stats.html" %}
<div class="row"> <div class="row">

View File

@ -25,7 +25,7 @@
</div> </div>
<!-- Page Title Header Ends--> <!-- Page Title Header Ends-->
{% include "parts/details_stats.html %} {% include "parts/details_stats.html" %}
<div class="row"> <div class="row">
@ -134,13 +134,15 @@
</style> </style>
<ul class="tree-view"> <ul class="tree-view">
<li> <li>
<div class="tree-caret tree-ctx-item files-tree-title"> <div class="tree-ctx-item" data-path="{{ data['server_stats']['server_id']['path'] }}">
<i class="far fa-folder"></i> <span id="{{ data['server_stats']['server_id']['path'] }}span" class="files-tree-title tree-caret-down" data-path="{{ data['server_stats']['server_id']['path'] }}" onclick="getToggleMain(event)">
<i class="far fa-folder-open"></i> <i class="far fa-folder"></i>
{{ translate('serverFiles', 'files', data['lang']) }} <i class="far fa-folder-open"></i>
{{ translate('serverFiles', 'files', data['lang']) }}
</span>
</div> </div>
<ul class="tree-nested d-block" id="files-tree"> <ul class="tree-nested d-block" id="files-tree">
<li>{{ translate('serverFiles', 'error', data['lang']) }}</li> <li><i class="fa fa-spin fa-spinner"></i>{{ translate('serverFiles', 'loadingRecords', data['lang']) }}</li>
</ul> </ul>
</li> </li>
@ -648,10 +650,13 @@
}, false); }, false);
}); });
} }
function getTreeView() { function getTreeView(event) {
path = '{{ data['server_stats']['server_id']['path'] }}'
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: '/ajax/get_tree?id={{ data['server_stats']['server_id']['server_id'] }}', url: '/ajax/get_tree?id={{ data['server_stats']['server_id']['server_id'] }}&path='+path,
dataType: 'text', dataType: 'text',
success: function(data){ success: function(data){
console.log("got response:"); console.log("got response:");
@ -661,26 +666,75 @@
serverDir = dataArr.shift(); // Remove & return first element (server directory) serverDir = dataArr.shift(); // Remove & return first element (server directory)
text = dataArr.join('\n'); text = dataArr.join('\n');
document.getElementById('files-tree').innerHTML = text; try{
document.getElementById(path).innerHTML += text;
event.target.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-path', serverDir);
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files'); document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
setTimeout(function () {setTreeViewContext()}, 1000); setTimeout(function () {setTreeViewContext()}, 1000);
},
});
}
var toggler = document.getElementsByClassName("tree-caret"); function getToggleMain(event) {
var i; 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");
}
for (i = 0; i < toggler.length; i++) { function getDirView(event) {
if (toggler[i].classList.contains('files-tree-title')) continue; path = event.target.parentElement.getAttribute('data-path');
toggler[i].addEventListener("click", function caretListener() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block"); if (document.getElementById(path).classList.contains('clicked')){
this.classList.toggle("tree-caret-down");
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_dir?id={{ data['server_stats']['server_id']['server_id'] }}&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")
}
setTimeout(function () {setTreeViewContext()}, 1000);
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");
}); });
} }
}, },
}); });
} }
}
function setTreeViewContext() { function setTreeViewContext() {
var treeItems = document.getElementsByClassName('tree-ctx-item'); var treeItems = document.getElementsByClassName('tree-ctx-item');
@ -875,11 +929,6 @@
}); });
} }
document.getElementsByClassName('files-tree-title')[0].addEventListener("click", function caretListener() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
this.classList.toggle("tree-caret-down");
});
getTreeView(); getTreeView();
setTreeViewContext(); setTreeViewContext();
@ -898,7 +947,7 @@
target.classList.add('btn-primary'); target.classList.add('btn-primary');
} }
window.onload = getTreeView();
</script> </script>

View File

@ -43,36 +43,51 @@
<div class="form-group"> <div class="form-group">
<label for="server_name">Action<small class="text-muted ml-1"></small> </label><br> <label for="server_name">Action<small class="text-muted ml-1"></small> </label><br>
<select id="action" name="action" onchange="yesnoCheck(this);" class="form-control form-control-lg select-css"> <select id="action" name="action" onchange="basicAdvanced(this);" class="form-control form-control-lg select-css">
<option value="start">Start Server</option> <option value="basic">Basic</option>
<option value="restart">Restart Server</option> <option value="advanced">Advanced</option>
<option value="shutdown">Shutdown Server</option>
<option value="command">Custon Command</option>
</select> </select>
</div> </div>
<div id="ifBasic">
<div class="form-group">
<label for="server_path">Interval <small class="text-muted ml-1"> - How often you want this task to execute</small> </label>
<input type="number" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="Interval" required>
<br>
<br>
<select id="interval_type" name="interval_type" class="form-control form-control-lg select-css">
<option value="days">Days</option>
<option value="hours">Hours</option>
<option value="minutes">Minutes</option>
<option value="weeks">Weeks</option>
</select>
</div>
<div class="form-group">
<label for="time">Time <small class="text-muted ml-1"> - What time do you want your task to execute?</small> </label>
<input type="time" class="form-control" name="time" id="time" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="Time" required>
</div>
<div id="ifYes" style="display: none;">
<div class="form-group"> <div class="form-group">
<label for="command">Command <small class="text-muted ml-1"> - What command do you want us to execute? Do not include the '/'</small> </label> <label for="server_name">Action<small class="text-muted ml-1"></small> </label><br>
<input type="input" class="form-control" name="command" id="command" value="" placeholder="Command" required> <select id="action" name="action" onchange="yesnoCheck(this);" class="form-control form-control-lg select-css">
<option value="start">Start Server</option>
<option value="restart">Restart Server</option>
<option value="shutdown">Shutdown Server</option>
<option value="command">Custon Command</option>
</select>
</div> </div>
<div class="form-group">
<label for="server_path">Interval <small class="text-muted ml-1"> - How often you want this task to execute</small> </label>
<input type="number" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="Interval" required>
<br>
<br>
<select id="interval_type" name="interval_type" class="form-control form-control-lg select-css">
<option value="days">Days</option>
<option value="hours">Hours</option>
<option value="minutes">Minutes</option>
<option value="weeks">Weeks</option>
</select>
</div>
<div class="form-group">
<label for="time">Time <small class="text-muted ml-1"> - What time do you want your task to execute?</small> </label>
<input type="time" class="form-control" name="time" id="time" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="Time" required>
</div>
<div id="ifYes" style="display: none;">
<div class="form-group">
<label for="command">Command <small class="text-muted ml-1"> - What command do you want us to execute? Do not include the '/'</small> </label>
<input type="input" class="form-control" name="command" id="command" value="" placeholder="Command" required>
</div>
</div>
</div>
<div id="ifAdvanced" style="display: none;">
<div class="form-group">
<label for="cron">Cron <small class="text-muted ml-1"> - Input your cron string</small> </label>
<input type="input" class="form-control" name="cron" id="cron" value="* * * * backup_server" placeholder="Cron" required>
</div>
</div> </div>
<div class="form-check-flat"> <div class="form-check-flat">
@ -173,6 +188,15 @@
document.getElementById("ifYes").style.display = "none"; document.getElementById("ifYes").style.display = "none";
} }
} }
function basicAdvanced(that) {
if (that.value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifBasic").style.display = "none";
} else {
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifBasic").style.display = "block";
}
}
</script> </script>

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', email=peewee.CharField(default="default@example.com"))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['email'])
"""
Write your rollback migrations here.
"""

View File

@ -212,7 +212,8 @@
"waitUpload": "Please wait while we upload your files... This may take a while.", "waitUpload": "Please wait while we upload your files... This may take a while.",
"stayHere": "DO NOT LEAVE THIS PAGE!", "stayHere": "DO NOT LEAVE THIS PAGE!",
"close": "Close", "close": "Close",
"download": "Download" "download": "Download",
"loadingRecords": "Loading Files..."
}, },
"serverConfig": { "serverConfig": {
"serverName": "Server Name", "serverName": "Server Name",

22
main.py
View File

@ -6,6 +6,7 @@ import time
import argparse import argparse
import logging.config import logging.config
import signal import signal
import threading
from app.classes.controllers.management_controller import Management_Controller from app.classes.controllers.management_controller import Management_Controller
""" Our custom classes / pip packages """ """ Our custom classes / pip packages """
@ -36,6 +37,22 @@ def do_intro():
console.magenta(intro) console.magenta(intro)
def check_port_thread():
port = helper.get_setting('https_port')
checked = False
while not checked:
if helper.check_server_conn(port):
checked = True
result_of_check = helper.check_port(port)
if result_of_check == True:
return
else:
console.warning("We have detected Crafty's port, {} may not be open on the host network or a firewall is blocking it. Remote client connections to Crafty may be limited.".format(helper.get_setting('https_port')))
console.help("If you are not forwarding ports from your public IP or your router does not support hairpin NAT you can safely disregard the previous message.")
else:
time.sleep(5)
return
def setup_logging(debug=True): def setup_logging(debug=True):
logging_config_file = os.path.join(os.path.curdir, logging_config_file = os.path.join(os.path.curdir,
@ -143,9 +160,8 @@ if __name__ == '__main__':
if not helper.check_internet(): if not helper.check_internet():
console.warning("We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.") console.warning("We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.")
elif not helper.check_port(helper.get_setting('https_port')): check_port_thread = threading.Thread(target=check_port_thread, daemon=True, name="crafty_port_check")
console.warning("We have detected Crafty's port, {} may not be open on the host network or a firewall is blocking it. Remote client connections to Crafty may be limited.".format(helper.get_setting('https_port'))) check_port_thread.start()
console.help("If you are not forwarding ports from your public IP or your router does not support hairpin NAT you can safely disregard the previous message.")
if not controller.check_system_user(): if not controller.check_system_user():
controller.add_system_user() controller.add_system_user()

View File

@ -3,6 +3,7 @@ argon2-cffi~=20.1
bleach~=3.1 bleach~=3.1
colorama~=0.4 colorama~=0.4
cryptography~=3.4 cryptography~=3.4
libgravatar~=1.0.0
peewee~=3.13 peewee~=3.13
pexpect~=4.8 pexpect~=4.8
psutil~=5.7 psutil~=5.7