Merge branch 'dev' into 'websocket'

# Conflicts:
#   app/classes/shared/models.py
#   app/classes/shared/tasks.py
This commit is contained in:
Phillip Tarrant 2021-03-07 21:29:57 +00:00
commit 85403c61af
32 changed files with 2382 additions and 404 deletions

View File

@ -25,13 +25,19 @@ except ModuleNotFoundError as e:
class ServerJars:
def __init__(self):
self.base_url = "https://serverjars.com"
def _get_api_result(self, call_url: str):
base_url = "https://serverjars.com"
full_url = "{base}{call_url}".format(base=base_url, call_url=call_url)
full_url = "{base}{call_url}".format(base=self.base_url, call_url=call_url)
r = requests.get(full_url, timeout=2)
try:
r = requests.get(full_url, timeout=2)
if r.status_code not in [200, 201]:
if r.status_code not in [200, 201]:
return {}
except Exception as e:
logger.error("Unable to connect to serverjar.com api due to error: {}".format(e))
return {}
try:
@ -66,18 +72,21 @@ class ServerJars:
data = self._read_cache()
return data.get('servers')
@staticmethod
def _check_api_alive():
def _check_api_alive(self):
logger.info("Checking serverjars.com API status")
check_url = "https://serverjars.com/api/fetchTypes"
r = requests.get(check_url, timeout=2)
check_url = "{base}/api/fetchTypes".format(base=self.base_url)
try:
r = requests.get(check_url, timeout=2)
if r.status_code in [200, 201]:
logger.info("Serverjars.com API is alive")
return True
if r.status_code in [200, 201]:
logger.info("Serverjars.com API is alive")
return True
except Exception as e:
logger.error("Unable to connect to serverjar.com api due to error: {}".format(e))
return {}
logger.error("unable to contact Serverjars.com api")
logger.error("unable to contact serverjars.com api")
return False
def refresh_cache(self):
@ -141,12 +150,11 @@ class ServerJars:
response = self._get_api_result(url)
return response
@staticmethod
def download_jar(server, version, path):
base_url = "https://serverjars.com/api/fetchJar/{server}/{version}".format(server=server, version=version)
def download_jar(self, server, version, path):
fetch_url = "{base}/api/fetchJar/{server}/{version}".format(base=self.base_url, server=server, version=version)
# open a file stream
with requests.get(base_url, timeout=2, stream=True) as r:
with requests.get(fetch_url, timeout=2, stream=True) as r:
try:
with open(path, 'wb') as output:
shutil.copyfileobj(r.raw, output)

View File

@ -19,12 +19,16 @@ class Stats:
def get_node_stats(self):
boot_time = datetime.datetime.fromtimestamp(psutil.boot_time())
data = {}
try:
cpu_freq = psutil.cpu_freq()
except NotImplementedError:
cpu_freq = psutil._common.scpufreq(current=0, min=0, max=0)
node_stats = {
'boot_time': str(boot_time),
'cpu_usage': psutil.cpu_percent(interval=0.5) / psutil.cpu_count(),
'cpu_count': psutil.cpu_count(),
'cpu_cur_freq': round(psutil.cpu_freq()[0], 2),
'cpu_max_freq': psutil.cpu_freq()[2],
'cpu_cur_freq': round(cpu_freq[0], 2),
'cpu_max_freq': cpu_freq[2],
'mem_percent': psutil.virtual_memory()[2],
'mem_usage': helper.human_readable_file_size(psutil.virtual_memory()[3]),
'mem_total': helper.human_readable_file_size(psutil.virtual_memory()[0]),

View File

@ -1,6 +1,6 @@
import datetime
import logging
from sys import modules
import sys
logger = logging.getLogger(__name__)
@ -11,18 +11,20 @@ try:
except ModuleNotFoundError as e:
logging.critical("Import Error: Unable to load {} module".format(e, e.name))
print("Import Error: Unable to load {} module".format(e, e.name))
pass
from app.classes.shared.installer import installer
installer.do_install()
sys.exit(1)
class Console:
def __init__(self):
if 'colorama' in modules:
if 'colorama' in sys.modules:
init()
@staticmethod
def do_print(message, color):
if 'termcolor' in modules or 'colorama' in modules:
if 'termcolor' in sys.modules or 'colorama' in sys.modules:
print(colored(message, color))
else:
print(message)

View File

@ -51,7 +51,7 @@ class Controller:
continue
# if this server path no longer exists - let's warn and bomb out
if not helper.check_path_exits(s['path']):
if not helper.check_path_exists(s['path']):
logger.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'],
s['path']))
@ -212,7 +212,9 @@ class Controller:
logger.error("Unable to create required server files due to :{}".format(e))
return False
server_command = 'java -Xms{}G -Xmx{}G -jar {} nogui'.format(min_mem, max_mem, full_jar_path)
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
server_log_file = "{}/logs/latest.log".format(server_dir)
server_stop = "stop"
@ -224,7 +226,7 @@ class Controller:
@staticmethod
def verify_jar_server( server_path: str, server_jar: str):
path_check = helper.check_path_exits(server_path)
path_check = helper.check_path_exists(server_path)
jar_check = helper.check_file_exists(os.path.join(server_path, server_jar))
if not path_check or not jar_check:
return False
@ -245,7 +247,9 @@ class Controller:
dir_util.copy_tree(server_path, new_server_dir)
full_jar_path = os.path.join(new_server_dir, server_jar)
server_command = 'java -Xms{}G -Xmx{}G -jar {} nogui'.format(min_mem, max_mem, full_jar_path)
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
server_log_file = "{}/logs/latest.log".format(new_server_dir)
server_stop = "stop"
@ -264,7 +268,10 @@ class Controller:
return "false"
full_jar_path = os.path.join(new_server_dir, server_jar)
server_command = 'java -Xms{}G -Xmx{}G -jar {} nogui'.format(min_mem, max_mem, full_jar_path)
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
print('command: ' + server_command)
server_log_file = "{}/logs/latest.log".format(new_server_dir)
server_stop = "stop"

View File

@ -9,6 +9,7 @@ import base64
import socket
import random
import logging
import html
from datetime import datetime
from socket import gethostname
@ -45,6 +46,10 @@ class Helpers:
self.passhasher = PasswordHasher()
self.exiting = False
def float_to_string(self, gbs: int):
s = str(float(gbs) * 1000).rstrip("0").rstrip(".")
return s
def check_file_perms(self, path):
try:
fp = open(path, "r").close()
@ -61,8 +66,27 @@ class Helpers:
return True
else:
return False
logger.error("{} does not exits".format(file))
return False
logger.error("{} does not exist".format(file))
return True
def check_for_old_logs(self, db_helper):
servers = db_helper.get_all_defined_servers()
for server in servers:
logs_path = os.path.split(server['log_path'])[0]
latest_log_file = os.path.split(server['log_path'])[1]
logs_delete_after = int(server['logs_delete_after'])
if logs_delete_after == 0:
continue
log_files = list(filter(
lambda val: val != latest_log_file,
os.listdir(logs_path)
))
for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file)
if self.check_file_exists(log_file_path) and \
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
os.remove(log_file_path)
def get_setting(self, key, default_return=False):
@ -280,7 +304,7 @@ class Helpers:
return "%.1f%s%s" % (num, 'Y', suffix)
@staticmethod
def check_path_exits(path: str):
def check_path_exists(path: str):
logger.debug('Looking for path: {}'.format(path))
if os.path.exists(path):
@ -463,6 +487,35 @@ class Helpers:
return data
@staticmethod
def generate_tree(folder, output=""):
for raw_filename in os.listdir(folder):
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 data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{}
</div>
\n<ul class="tree-nested">"""\
.format(os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
output += helper.generate_tree(rel)
output += '</ul>\n</li>'
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
@staticmethod
def in_path(x, y):
return os.path.abspath(y).__contains__(os.path.abspath(x))
helper = Helpers()

View File

@ -0,0 +1,25 @@
import sys
import subprocess
class install:
@staticmethod
def is_venv():
return (hasattr(sys, 'real_prefix') or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))
def do_install(self):
# are we in a venv?
if not self.is_venv():
print("Crafty Requires a venv to install")
sys.exit(1)
# do our pip install
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", 'requirements.txt'])
print("Crafty has installed it's dependencies, please restart Crafty")
sys.exit(0)
installer = install()

View File

@ -34,29 +34,49 @@ class Users(BaseModel):
user_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
last_login = DateTimeField(default=datetime.datetime.now)
last_update = DateTimeField(default=datetime.datetime.now)
last_ip = CharField(default="")
username = CharField(default="")
username = CharField(default="", unique=True, index=True)
password = CharField(default="")
enabled = BooleanField(default=True)
api_token = CharField(default="")
allowed_servers = CharField(default="[]")
superuser = BooleanField(default=False)
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
class Meta:
table_name = "users"
class Roles(BaseModel):
role_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
last_update = DateTimeField(default=datetime.datetime.now)
role_name = CharField(default="", unique=True, index=True)
class Meta:
table_name = "roles"
class User_Roles(BaseModel):
user_id = ForeignKeyField(Users, backref='user_role')
role_id = ForeignKeyField(Roles, backref='user_role')
class Meta:
table_name = 'user_roles'
primary_key = CompositeKey('user_id', 'role_id')
class Audit_Log(BaseModel):
audit_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
user_name = CharField(default="")
user_id = IntegerField(default=0)
user_id = IntegerField(default=0, index=True)
source_ip = CharField(default='127.0.0.1')
server_id = IntegerField(default=None)
server_id = IntegerField(default=None, index=True) # When auditing global events, use server ID 0
log_msg = TextField(default='')
class Host_Stats(BaseModel):
time = DateTimeField(default=datetime.datetime.now)
time = DateTimeField(default=datetime.datetime.now, index=True)
boot_time = CharField(default="")
cpu_usage = FloatField(default=0)
cpu_cores = IntegerField(default=0)
@ -74,8 +94,8 @@ class Host_Stats(BaseModel):
class Servers(BaseModel):
server_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_uuid = CharField(default="")
server_name = CharField(default="Server")
server_uuid = CharField(default="", index=True)
server_name = CharField(default="Server", index=True)
path = CharField(default="")
executable = CharField(default="")
log_path = CharField(default="")
@ -86,15 +106,34 @@ class Servers(BaseModel):
stop_command = CharField(default="stop")
server_ip = CharField(default="127.0.0.1")
server_port = IntegerField(default=25565)
logs_delete_after = IntegerField(default=0)
class Meta:
table_name = "servers"
class User_Servers(BaseModel):
user_id = ForeignKeyField(Users, backref='user_server')
server_id = ForeignKeyField(Servers, backref='user_server')
class Meta:
table_name = 'user_servers'
primary_key = CompositeKey('user_id', 'server_id')
class Role_Servers(BaseModel):
role_id = ForeignKeyField(Roles, backref='role_server')
server_id = ForeignKeyField(Servers, backref='role_server')
class Meta:
table_name = 'role_servers'
primary_key = CompositeKey('role_id', 'server_id')
class Server_Stats(BaseModel):
stats_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server')
server_id = ForeignKeyField(Servers, backref='server', index=True)
started = CharField(default="")
running = BooleanField(default=False)
cpu = FloatField(default=0)
@ -118,8 +157,8 @@ class Server_Stats(BaseModel):
class Commands(BaseModel):
command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server')
user = ForeignKeyField(Users, backref='user')
server_id = ForeignKeyField(Servers, backref='server', index=True)
user = ForeignKeyField(Users, backref='user', index=True)
source_ip = CharField(default='127.0.0.1')
command = CharField(default='')
executed = BooleanField(default=False)
@ -130,7 +169,7 @@ class Commands(BaseModel):
class Webhooks(BaseModel):
id = AutoField()
name = CharField(max_length=64, unique=True)
name = CharField(max_length=64, unique=True, index=True)
method = CharField(default="POST")
url = CharField(unique=True)
event = CharField(default="")
@ -144,7 +183,7 @@ class Backups(BaseModel):
directories = CharField()
storage_location = CharField()
max_backups = IntegerField()
server_id = IntegerField()
server_id = IntegerField(index=True)
class Meta:
table_name = 'backups'
@ -158,9 +197,13 @@ class db_builder:
database.create_tables([
Backups,
Users,
Roles,
User_Roles,
Host_Stats,
Webhooks,
Servers,
User_Servers,
Role_Servers,
Server_Stats,
Commands,
Audit_Log
@ -174,16 +217,18 @@ class db_builder:
username = default_data.get("username", 'admin')
password = default_data.get("password", 'crafty')
api_token = helper.random_string_generator(32)
Users.insert({
Users.username: username.lower(),
Users.password: helper.encode_pass(password),
Users.api_token: api_token,
Users.enabled: True
}).execute()
#api_token = helper.random_string_generator(32)
#
#Users.insert({
# Users.username: username.lower(),
# Users.password: helper.encode_pass(password),
# Users.api_token: api_token,
# Users.enabled: True,
# Users.superuser: True
#}).execute()
db_shortcuts.add_user(username, password=password, superuser=True)
console.info("API token is {}".format(api_token))
#console.info("API token is {}".format(api_token))
@staticmethod
def is_fresh_install():
@ -197,7 +242,8 @@ class db_builder:
class db_shortcuts:
def return_rows(self, query):
@staticmethod
def return_rows(query):
rows = []
try:
@ -210,7 +256,8 @@ class db_shortcuts:
return rows
def get_server_data_by_id(self, server_id):
@staticmethod
def get_server_data_by_id(server_id):
try:
query = Servers.get_by_id(server_id)
except DoesNotExist:
@ -218,25 +265,29 @@ class db_shortcuts:
return model_to_dict(query)
def get_all_defined_servers(self):
@staticmethod
def get_all_defined_servers():
query = Servers.select()
return self.return_rows(query)
return db_helper.return_rows(query)
def get_all_servers_stats(self):
servers = self.get_all_defined_servers()
@staticmethod
def get_all_servers_stats():
servers = db_helper.get_all_defined_servers()
server_data = []
for s in servers:
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
server_data.append({'server_data': s, "stats": self.return_rows(latest)})
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)})
return server_data
def get_server_stats_by_id(self, server_id):
@staticmethod
def get_server_stats_by_id(server_id):
stats = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).limit(1)
return self.return_rows(stats)
return db_helper.return_rows(stats)
def server_id_exists(self, server_id):
if not self.get_server_data_by_id(server_id):
@staticmethod
def server_id_exists(server_id):
if not db_helper.get_server_data_by_id(server_id):
return False
return True
@ -245,13 +296,197 @@ class db_shortcuts:
query = Host_Stats.select().order_by(Host_Stats.id.desc()).get()
return model_to_dict(query)
def get_all_users(self):
@staticmethod
def new_api_token():
while True:
token = helper.random_string_generator(32)
test = list(Users.select(Users.user_id).where(Users.api_token == token))
if len(test) == 0:
return token
@staticmethod
def get_all_users():
query = Users.select()
return query
def get_unactioned_commands(self):
@staticmethod
def get_all_roles():
query = Roles.select()
return query
@staticmethod
def get_userid_by_name(username):
try:
return (Users.get(Users.username == username)).user_id
except DoesNotExist:
return None
@staticmethod
def get_user(user_id):
user = model_to_dict(Users.get(Users.user_id == user_id))
if user:
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
else:
logger.debug("user: ({}) {}".format(user_id, {}))
return {}
@staticmethod
def update_user(user_id, user_data={}):
base_data = db_helper.get_user(user_id)
up_data = {}
added_roles = set()
removed_roles = set()
added_servers = set()
removed_servers = set()
for key in user_data:
if key == "user_id":
continue
elif key == "roles":
added_roles = user_data['roles'].difference(base_data['roles'])
removed_roles = base_data['roles'].difference(user_data['roles'])
elif key == "servers":
added_servers = user_data['servers'].difference(base_data['servers'])
removed_servers = base_data['servers'].difference(user_data['servers'])
elif key == "regen_api":
if user_data['regen_api']:
up_data['api_token'] = db_shortcuts.new_api_token()
elif key == "password":
if user_data['password'] is not None and user_data['password'] != "":
up_data['password'] = helper.encode_pass(user_data['password'])
elif base_data[key] != user_data[key]:
up_data[key] = user_data[key]
up_data['last_update'] = helper.get_time_as_string()
logger.debug("user: {} +role:{} -role:{} +server:{} -server{}".format(user_data, added_roles, removed_roles, added_servers, removed_servers))
with database.atomic():
for role in added_roles:
User_Roles.get_or_create(user_id=user_id, role_id=role)
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
User_Roles.delete().where(User_Roles.user_id == user_id).where(User_Roles.role_id.in_(removed_roles)).execute()
for server in added_servers:
User_Servers.get_or_create(user_id=user_id, server_id=server)
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
User_Servers.delete().where(User_Servers.user_id == user_id).where(User_Servers.server_id.in_(removed_servers)).execute()
if up_data:
Users.update(up_data).where(Users.user_id == user_id).execute()
@staticmethod
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
if password is not None:
pw_enc = helper.encode_pass(password)
else:
pw_enc = None
if api_token is None:
api_token = db_shortcuts.new_api_token()
else:
if type(api_token) is not str and len(api_token) != 32:
raise ValueError("API token must be a 32 character string")
user_id = Users.insert({
Users.username: username.lower(),
Users.password: pw_enc,
Users.api_token: api_token,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: helper.get_time_as_string()
}).execute()
return user_id
@staticmethod
def remove_user(user_id):
user = Users.get(Users.user_id == user_id)
return user.delete_instance()
@staticmethod
def user_id_exists(user_id):
if not db_shortcuts.get_user(user_id):
return False
return True
@staticmethod
def get_roleid_by_name(role_name):
try:
return (Roles.get(Roles.role_name == role_name)).role_id
except DoesNotExist:
return None
@staticmethod
def get_role(role_id):
role = model_to_dict(Roles.get(Roles.role_id == role_id))
if role:
servers_query = Role_Servers.select().join(Servers, JOIN.INNER).where(Role_Servers.role_id == role_id)
# TODO: this query needs to be narrower
servers = set()
for s in servers_query:
servers.add(s.server_id.server_id)
role['servers'] = servers
logger.debug("role: ({}) {}".format(role_id, role))
return role
else:
logger.debug("role: ({}) {}".format(role_id, {}))
return {}
@staticmethod
def update_role(role_id, role_data={}):
base_data = db_helper.get_role(role_id)
up_data = {}
added_servers = set()
removed_servers = set()
for key in role_data:
if key == "role_id":
continue
elif key == "servers":
added_servers = role_data['servers'].difference(base_data['servers'])
removed_servers = base_data['servers'].difference(role_data['servers'])
elif base_data[key] != role_data[key]:
up_data[key] = role_data[key]
up_data['last_update'] = helper.get_time_as_string()
logger.debug("role: {} +server:{} -server{}".format(role_data, added_servers, removed_servers))
with database.atomic():
for server in added_servers:
Role_Servers.get_or_create(role_id=role_id, server_id=server)
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute()
if up_data:
Roles.update(up_data).where(Roles.role_id == role_id).execute()
@staticmethod
def add_role(role_name):
role_id = Roles.insert({
Roles.role_name: role_name.lower(),
Roles.created: helper.get_time_as_string()
}).execute()
return role_id
@staticmethod
def remove_role(role_id):
role = Roles.get(Roles.role_id == role_id)
return role.delete_instance()
@staticmethod
def role_id_exists(role_id):
if not db_shortcuts.get_role(role_id):
return False
return True
@staticmethod
def get_unactioned_commands():
query = Commands.select().where(Commands.executed == 0)
return self.return_rows(query)
return db_helper.return_rows(query)
@staticmethod
def get_server_friendly_name(server_id):
@ -275,11 +510,13 @@ class db_shortcuts:
Commands.command: command
}).execute()
def get_actity_log(self):
@staticmethod
def get_actity_log():
q = Audit_Log.select()
return self.return_db_rows(q)
return db_helper.return_db_rows(q)
def return_db_rows(self, model):
@staticmethod
def return_db_rows(model):
data = [model_to_dict(row) for row in model]
return data

View File

@ -97,7 +97,7 @@ class Server:
console.critical("Server executable path: {} does not seem to exist".format(full_path))
helper.do_exit()
if not helper.check_path_exits(self.server_path):
if not helper.check_path_exists(self.server_path):
logger.critical("Server path: {} does not seem to exits".format(self.server_path))
console.critical("Server path: {} does not seem to exits".format(self.server_path))
helper.do_exit()

View File

@ -38,6 +38,9 @@ class TasksManager:
self.schedule_thread = threading.Thread(target=self.scheduler_thread, daemon=True, name="scheduler")
self.log_watcher_thread = threading.Thread(target=self.log_watcher, daemon=True, name="log_watcher")
self.log_watcher_thread.start()
self.command_thread = threading.Thread(target=self.command_watcher, daemon=True, name="command_watcher")
self.command_thread.start()

View File

@ -3,6 +3,8 @@ import logging
import tornado.web
import tornado.escape
import bleach
import os
import shutil
from app.classes.shared.console import console
from app.classes.shared.models import Users, installer
@ -55,7 +57,7 @@ class AjaxHandler(BaseHandler):
self.redirect("/panel/error?error=Server ID Not Found")
if server_data['log_path']:
logger.warning("Server ID not found in server_log ajax call")
logger.warning("Server ID not found in server_log ajax call ({})".format(server_id))
if full_log:
log_lines = helper.get_setting('max_log_lines')
@ -79,7 +81,57 @@ class AjaxHandler(BaseHandler):
page_data['notify_data'] = data
self.render_page('ajax/notify.html', page_data)
elif page == "get_file":
file_path = self.get_argument('file_path', None)
server_id = self.get_argument('id', None)
if server_id is None:
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in get_file ajax call ({})".format(server_id))
console.warning("Server ID not found in get_file ajax call ({})".format(server_id))
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in get_file ajax call ({})".format(file_path))
console.warning("Invalid path in get_file ajax call ({})".format(file_path))
return False
file = open(file_path)
file_contents = file.read()
file.close()
self.write(file_contents)
self.finish()
elif page == "get_tree":
server_id = self.get_argument('id', None)
if server_id is None:
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in get_file ajax call ({})".format(server_id))
console.warning("Server ID not found in get_file ajax call ({})".format(server_id))
return False
self.write(db_helper.get_server_data_by_id(server_id)['path'] + '\n' +
helper.generate_tree(db_helper.get_server_data_by_id(server_id)['path']))
self.finish()
@tornado.web.authenticated
def post(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
error = bleach.clean(self.get_argument('error', "WTF Error!"))
@ -90,11 +142,12 @@ class AjaxHandler(BaseHandler):
}
if page == "send_command":
command = bleach.clean(self.get_body_argument('command', default=None, strip=True))
server_id = bleach.clean(self.get_argument('id'))
command = self.get_body_argument('command', default=None, strip=True)
server_id = self.get_argument('id')
if server_id is None:
logger.warning("Server ID not found in send_command ajax call")
console.warning("Server ID not found in send_command ajax call")
srv_obj = controller.get_server_obj(server_id)
@ -102,3 +155,192 @@ class AjaxHandler(BaseHandler):
if srv_obj.check_running():
srv_obj.send_command(command)
elif page == "create_file":
file_parent = self.get_body_argument('file_parent', default=None, strip=True)
file_name = self.get_body_argument('file_name', default=None, strip=True)
file_path = os.path.join(file_parent, file_name)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in create_file ajax call")
console.warning("Server ID not found in create_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in create_file ajax call ({})".format(server_id))
console.warning("Server ID not found in create_file ajax call ({})".format(server_id))
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
or helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in create_file ajax call ({})".format(file_path))
console.warning("Invalid path in create_file ajax call ({})".format(file_path))
return False
# Create the file by opening it
with open(file_path, 'w') as file_object:
file_object.close()
elif page == "create_dir":
dir_parent = self.get_body_argument('dir_parent', default=None, strip=True)
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
dir_path = os.path.join(dir_parent, dir_name)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in create_dir ajax call")
console.warning("Server ID not found in create_dir ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in create_dir ajax call ({})".format(server_id))
console.warning("Server ID not found in create_dir ajax call ({})".format(server_id))
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
or helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
console.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
return False
# Create the directory
os.mkdir(dir_path)
@tornado.web.authenticated
def delete(self, page):
if page == "del_file":
file_path = self.get_body_argument('file_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in del_file ajax call ({})".format(server_id))
console.warning("Server ID not found in del_file ajax call ({})".format(server_id))
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in del_file ajax call ({})".format(file_path))
console.warning("Invalid path in del_file ajax call ({})".format(file_path))
return False
# Delete the file
os.remove(file_path)
elif page == "del_dir":
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in del_file ajax call ({})".format(server_id))
console.warning("Server ID not found in del_file ajax call ({})".format(server_id))
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
or not helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in del_file ajax call ({})".format(dir_path))
console.warning("Invalid path in del_file ajax call ({})".format(dir_path))
return False
# Delete the file
# os.rmdir(dir_path)
shutil.rmtree(dir_path) # Removes also when there are contents
@tornado.web.authenticated
def put(self, page):
if page == "save_file":
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
file_path = self.get_body_argument('file_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(file_contents)
print(file_path)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in save_file ajax call")
console.warning("Server ID not found in save_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in save_file ajax call ({})".format(server_id))
console.warning("Server ID not found in save_file ajax call ({})".format(server_id))
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in save_file ajax call ({})".format(file_path))
console.warning("Invalid path in save_file ajax call ({})".format(file_path))
return False
# Open the file in write mode and store the content in file_object
with open(file_path, 'w') as file_object:
file_object.write(file_contents)
elif page == "rename_item":
item_path = self.get_body_argument('item_path', default=None, strip=True)
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in rename_item ajax call ({})".format(server_id))
console.warning("Server ID not found in rename_item ajax call ({})".format(server_id))
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in rename_item ajax call ({})".format(server_id))
console.warning("Server ID not found in rename_item ajax call ({})".format(server_id))
return False
if item_path is None or new_item_name is None:
logger.warning("Invalid path in rename_item ajax call")
console.warning("Invalid path in rename_item ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], item_path) \
or not helper.check_path_exists(os.path.abspath(item_path)):
logger.warning("Invalid path in rename_item ajax call ({})".format(server_id))
console.warning("Invalid path in rename_item ajax call ({})".format(server_id))
return False
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], new_item_path) \
or helper.check_path_exists(os.path.abspath(new_item_path)):
logger.warning("Invalid path 2 in rename_item ajax call ({})".format(server_id))
console.warning("Invalid path 2 in rename_item ajax call ({})".format(server_id))
return False
# RENAME
os.rename(item_path, new_item_path)

View File

@ -3,6 +3,8 @@ import logging
import tornado.web
import tornado.escape
import bleach
import time
import datetime
from app.classes.shared.console import console
from app.classes.shared.models import Users, installer
@ -23,6 +25,9 @@ class PanelHandler(BaseHandler):
template = "panel/denied.html"
now = time.time()
formatted_time = str(datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S'))
defined_servers = controller.list_defined_servers()
page_data = {
@ -38,7 +43,8 @@ class PanelHandler(BaseHandler):
'menu_servers': defined_servers,
'hosts_data': db_helper.get_latest_hosts_stats(),
'show_contribute': helper.get_setting("show_contribute_link", True),
'error': error
'error': error,
'time': formatted_time
}
# if no servers defined, let's go to the build server area
@ -59,12 +65,6 @@ class PanelHandler(BaseHandler):
elif page == 'contribute':
template = "panel/contribute.html"
elif page == 'file_edit':
template = "panel/file_edit.html"
elif page == 'files_menu':
template = "panel/files_menu.html"
elif page == "remove_server":
server_id = self.get_argument('id', None)
server_data = controller.get_server_data(server_id)
@ -106,10 +106,12 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID")
return False
valid_subpages = ['term', 'logs', 'config']
valid_subpages = ['term', 'logs', 'config', 'files']
if subpage not in valid_subpages:
logger.debug('not a valid subpage')
subpage = 'term'
logger.debug('Subpage: "{}"'.format(subpage))
# server_data isn't needed since the server_stats also pulls server data
# page_data['server_data'] = db_helper.get_server_data_by_id(server_id)
@ -120,9 +122,150 @@ class PanelHandler(BaseHandler):
elif page == 'panel_config':
page_data['users'] = db_helper.get_all_users()
# print(page_data['users'])
page_data['roles'] = db_helper.get_all_roles()
exec_user = db_helper.get_user(user_data['user_id'])
for user in page_data['users']:
if user.user_id != exec_user['user_id']:
user.api_token = "********"
template = "panel/panel_config.html"
elif page == "add_user":
page_data['new_user'] = True
page_data['user'] = {}
page_data['user']['username'] = ""
page_data['user']['user_id'] = -1
page_data['user']['enabled'] = True
page_data['user']['superuser'] = False
page_data['user']['api_token'] = "N/A"
page_data['user']['created'] = "N/A"
page_data['user']['last_login'] = "N/A"
page_data['user']['last_ip'] = "N/A"
page_data['role']['last_update'] = "N/A"
page_data['user']['roles'] = set()
page_data['user']['servers'] = set()
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
page_data['roles_all'] = db_helper.get_all_roles()
page_data['servers_all'] = controller.list_defined_servers()
template = "panel/panel_edit_user.html"
elif page == "edit_user":
page_data['new_user'] = False
user_id = self.get_argument('id', None)
page_data['user'] = db_helper.get_user(user_id)
page_data['roles_all'] = db_helper.get_all_roles()
page_data['servers_all'] = controller.list_defined_servers()
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
if exec_user['user_id'] != page_data['user']['user_id']:
page_data['user']['api_token'] = "********"
template = "panel/panel_edit_user.html"
elif page == "remove_user":
user_id = bleach.clean(self.get_argument('id', None))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
else:
# does this user id exist?
target_user = db_helper.get_user(user_id)
if not target_user:
self.redirect("/panel/error?error=Invalid User ID")
return False
elif target_user['superuser']:
self.redirect("/panel/error?error=Cannot remove a superuser")
return False
db_helper.remove_user(user_id)
db_helper.add_to_audit_log(exec_user['user_id'],
"Removed user {} (UID:{})".format(target_user['username'], user_id),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
elif page == "add_role":
page_data['new_role'] = True
page_data['role'] = {}
page_data['role']['role_name'] = ""
page_data['role']['role_id'] = -1
page_data['role']['created'] = "N/A"
page_data['role']['last_update'] = "N/A"
page_data['role']['servers'] = set()
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
page_data['servers_all'] = controller.list_defined_servers()
template = "panel/panel_edit_role.html"
elif page == "edit_role":
page_data['new_role'] = False
role_id = self.get_argument('id', None)
page_data['role'] = db_helper.get_role(role_id)
page_data['servers_all'] = controller.list_defined_servers()
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return False
template = "panel/panel_edit_role.html"
elif page == "remove_role":
role_id = bleach.clean(self.get_argument('id', None))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return False
else:
# does this user id exist?
target_role = db_helper.get_user(role_id)
if not target_role:
self.redirect("/panel/error?error=Invalid Role ID")
return False
db_helper.remove_role(role_id)
db_helper.add_to_audit_log(exec_user['user_id'],
"Removed role {} (RID:{})".format(target_role['role_name'], role_id),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
elif page == "activity_logs":
page_data['audit_logs'] = db_helper.get_actity_log()
@ -130,7 +273,9 @@ class PanelHandler(BaseHandler):
self.render(
template,
data=page_data
data=page_data,
time=time,
utc_offset=(time.timezone * -1 / 60 / 60),
)
@tornado.web.authenticated
@ -149,9 +294,16 @@ class PanelHandler(BaseHandler):
server_port = self.get_argument('server_port', None)
auto_start = int(float(self.get_argument('auto_start', '0')))
crash_detection = int(float(self.get_argument('crash_detection', '0')))
logs_delete_after = int(float(self.get_argument('logs_delete_after', '0')))
subpage = self.get_argument('subpage', None)
if server_id is None:
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user.superuser:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return False
else:
@ -174,15 +326,223 @@ class PanelHandler(BaseHandler):
Servers.server_port: server_port,
Servers.auto_start: auto_start,
Servers.crash_detection: crash_detection,
Servers.logs_delete_after: logs_delete_after,
}).where(Servers.server_id == server_id).execute()
controller.refresh_server_settings(server_id)
user_data = json.loads(self.get_secure_cookie("user_data"))
db_helper.add_to_audit_log(user_data['user_id'],
"Edited server {} named {}".format(server_id, server_name),
server_id,
self.get_remote_ip())
self.redirect("/panel/server_detail?id={}&subpage=config".format(server_id))
elif page == "edit_user":
user_id = bleach.clean(self.get_argument('id', None))
username = bleach.clean(self.get_argument('username', None))
password0 = bleach.clean(self.get_argument('password0', None))
password1 = bleach.clean(self.get_argument('password1', None))
enabled = int(float(bleach.clean(self.get_argument('enabled'), '0')))
regen_api = int(float(bleach.clean(self.get_argument('regen_api', '0'))))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return False
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
else:
# does this user id exist?
if not db_helper.user_id_exists(user_id):
self.redirect("/panel/error?error=Invalid User ID")
return False
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
return False
roles = set()
for role in db_helper.get_all_roles():
argument = int(float(
bleach.clean(
self.get_argument('role_{}_membership'.format(role.role_id), '0')
)
))
if argument:
roles.add(role.role_id)
servers = set()
for server in controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
)
))
if argument:
servers.add(server['server_id'])
user_data = {
"username": username,
"password": password0,
"enabled": enabled,
"regen_api": regen_api,
"roles": roles,
"servers": servers
}
db_helper.update_user(user_id, user_data=user_data)
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited user {} (UID:{}) with roles {} and servers {}".format(username, user_id, roles, servers),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
elif page == "add_user":
username = bleach.clean(self.get_argument('username', None))
password0 = bleach.clean(self.get_argument('password0', None))
password1 = bleach.clean(self.get_argument('password1', None))
enabled = int(float(bleach.clean(self.get_argument('enabled'), '0')))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return False
else:
# does this user id exist?
if db_helper.get_userid_by_name(username) is not None:
self.redirect("/panel/error?error=User exists")
return False
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
return False
roles = set()
for role in db_helper.get_all_roles():
argument = int(float(
bleach.clean(
self.get_argument('role_{}_membership'.format(role.role_id), '0')
)
))
if argument:
roles.add(role['role_id'])
servers = set()
for server in controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
)
))
if argument:
servers.add(server['server_id'])
user_id = db_helper.add_user(username, password=password0, enabled=enabled)
db_helper.update_user(user_id, {"roles":roles, "servers": servers})
db_helper.add_to_audit_log(exec_user['user_id'],
"Added user {} (UID:{})".format(username, user_id),
server_id=0,
source_ip=self.get_remote_ip())
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited user {} (UID:{}) with roles {} and servers {}".format(username, user_id, roles, servers),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
elif page == "edit_role":
role_id = bleach.clean(self.get_argument('id', None))
role_name = bleach.clean(self.get_argument('role_name', None))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif role_name is None or role_name == "":
self.redirect("/panel/error?error=Invalid username")
return False
elif role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return False
else:
# does this user id exist?
if not db_helper.role_id_exists(role_id):
self.redirect("/panel/error?error=Invalid Role ID")
return False
servers = set()
for server in controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
)
))
if argument:
servers.add(server['server_id'])
role_data = {
"role_name": role_name,
"servers": servers
}
db_helper.update_role(role_id, role_data=role_data)
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited role {} (RID:{}) with servers {}".format(role_name, role_id, servers),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
elif page == "add_role":
role_name = bleach.clean(self.get_argument('role_name', None))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif role_name is None or role_name == "":
self.redirect("/panel/error?error=Invalid role name")
return False
else:
# does this user id exist?
if db_helper.get_roleid_by_name(role_name) is not None:
self.redirect("/panel/error?error=Role exists")
return False
servers = set()
for server in controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
)
))
if argument:
servers.add(server['server_id'])
role_id = db_helper.add_role(role_name)
db_helper.update_role(role_id, {"servers": servers})
db_helper.add_to_audit_log(exec_user['user_id'],
"Added role {} (RID:{})".format(role_name, role_id),
server_id=0,
source_ip=self.get_remote_ip())
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited role {} (RID:{}) with servers {}".format(role_name, role_id, servers),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")

View File

@ -106,7 +106,7 @@ class PublicHandler(BaseHandler):
cookie_data = {
"username": user_data.username,
"user_id": user_data.user_id,
"account_type": user_data.allowed_servers,
"account_type": user_data.superuser,
}
self.set_secure_cookie('user_data', json.dumps(cookie_data))

View File

@ -1,6 +1,8 @@
import sys
import json
import logging
import os
import shutil
from app.classes.shared.console import console
from app.classes.web.base_handler import BaseHandler
@ -75,6 +77,64 @@ class ServerHandler(BaseHandler):
command = bleach.clean(self.get_argument("command", None))
if server_id is not None:
if command == "clone_server":
def is_name_used(name):
for server in db_helper.get_all_defined_servers():
if server['server_name'] == name:
return True
return False
server_data = db_helper.get_server_data_by_id(server_id)
server_uuid = server_data.get('server_uuid')
new_server_name = server_data.get('server_name') + " (Copy)"
name_counter = 1
while is_name_used(new_server_name):
name_counter += 1
new_server_name = server_data.get('server_name') + " (Copy {})".format(name_counter)
console.debug('new_server_name: "{}"'.format(new_server_name))
new_server_uuid = helper.create_uuid()
while os.path.exists(os.path.join(helper.servers_dir, new_server_uuid)):
new_server_uuid = helper.create_uuid()
new_server_path = os.path.join(helper.servers_dir, new_server_uuid)
# copy the old server
shutil.copytree(server_data.get('path'), new_server_path)
# TODO get old server DB data to individual variables
stop_command = server_data.get('stop_command')
new_server_command = str(server_data.get('execution_command')).replace(server_uuid, new_server_uuid)
new_executable = server_data.get('executable')
new_server_log_file = str(server_data.get('log_path')).replace(server_uuid, new_server_uuid)
auto_start = server_data.get('auto_start')
auto_start_delay = server_data.get('auto_start_delay')
crash_detection = server_data.get('crash_detection')
server_port = server_data.get('server_port')
# TODO create the server on the DB side
Servers.insert({
Servers.server_name: new_server_name,
Servers.server_uuid: new_server_uuid,
Servers.path: new_server_path,
Servers.executable: new_executable,
Servers.execution_command: new_server_command,
Servers.auto_start: auto_start,
Servers.auto_start_delay: auto_start_delay,
Servers.crash_detection: crash_detection,
Servers.log_path: new_server_log_file,
Servers.server_port: server_port,
Servers.stop_command: stop_command
}).execute()
controller.init_all_servers()
console.debug('initted all servers')
return
db_helper.send_command(user_data['user_id'], server_id, self.get_remote_ip(), command)
if page == "step1":

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -2,7 +2,7 @@
<footer class="footer">
<div class="container-fluid ">
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">Copyright © 2020 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. All rights reserved.</span>
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">Copyright © 2021 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. All rights reserved.</span>
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">Version: {{ data['version_data'] }}
</span>
</div>

View File

@ -64,6 +64,14 @@
</li>
{% end %}
<!--
<li class="nav-item">
<a class="nav-link" href="/panel/files?id=1">
<i class="fas fa-copy"></i> &nbsp;
<span class="menu-title">Files Test</span>
</a>
</li>-->
</ul>
</nav>

View File

@ -375,6 +375,47 @@
</div>
</div>
<div class="col-md-6 mb-5">
<div class="card rounded shadow-none">
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="user-avatar mb-auto">
<img src="/static/assets/images/credits/lukas_cropped.png"
alt="profile image" class="profile-img img-lg rounded-circle">
</div>
<div class="wrapper">
<div class="wrapper d-flex align-items-center">
<h4 class="mb-0 font-weight-medium">ThatOneLukas</h4>
</div>
<div class="wrapper d-flex align-items-center font-weight-medium text-muted">
<i class="mdi mdi-map-marker-outline mr-2"></i>
<p class="mb-0 text-muted">Helsinki, FI</p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="wrapper d-flex align-items-start">
<span class="btn btn-sm btn-info mr-2">Staff</span>
<span class="btn btn-sm btn-primary mr-2">Developer</span>
</div>
<div class="wrapper d-flex align-items-start pt-3">
His interests include programming, gaming, and electronics.
He likes gaming, programming, messing around with electronics, and time with his family.
</div>
</div>
</div>
</div>
</div>
</div>
</div> <!-- end user row-->
</div>
@ -471,6 +512,7 @@
</div>
</div>
<!-- content-wrapper ends -->

View File

@ -130,6 +130,7 @@
<a class="restart_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-sync"></i></a> &nbsp;
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a> &nbsp;
{% end %}
</td>
@ -217,7 +218,7 @@
<script>
function send_command (server_id, command){
<!-- this getCookie function is in base.html-->
/* this getCookie function is in base.html */
var token = getCookie("_xsrf");
$.ajax({
@ -281,6 +282,16 @@ $( document ).ready(function() {
});
}
$( ".clone_button" ).click(function() {
server_id = $(this).attr("data-id");
send_command(server_id, 'clone_server');
bootbox.alert({
backdrop: true,
title: "Sending your command",
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; Please be patient while we clone the server<br /> This screen will refresh in a moment </div>'
});
});
});
</script>

View File

@ -1,190 +0,0 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Editing File -< name >- in server -< server name >- (-< server path id >-){% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Editing File -< name >- in server -< server name >- (-< server path id >-)
<br />
<small>Path: -< file location relative to server directory >-</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body">
<a href="/" class="btn btn-link btn-outline-primary" style="position: relative;top: -1rem">
<i class="fas fa-arrow-left"></i> Back
</a>
<h3 id="file_warn"></h3>
<div id="editor">-< file_contents >-</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script src="/static/assets/vendors/ace-builds/src-min/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
let editor = ace.edit('editor');
editor.setTheme('ace/theme/dracula');
let extensionChanges = [
{
regex: /^js$/,
replaceWith: 'ace/mode/javascript'
},
{
regex: /^py$/,
replaceWith: 'ace/mode/python'
},
{
regex: /^html$/,
replaceWith: 'ace/mode/html'
},
{
regex: /^yml$/,
replaceWith: 'ace/mode/yaml'
},
{
regex: /^yaml$/,
replaceWith: 'ace/mode/yaml'
},
{
regex: /^txt$/,
replaceWith: 'ace/mode/text'
},
{
regex: /^json$/,
replaceWith: 'ace/mode/json'
},
{
regex: /^java$/,
replaceWith: 'ace/mode/java'
},
{
regex: /^cpp$/,
replaceWith: 'ace/mode/c_cpp'
},
{
regex: /^css$/,
replaceWith: 'ace/mode/css'
},
{
regex: /^scss$/,
replaceWith: 'ace/mode/scss'
},
{
regex: /^sass$/,
replaceWith: 'ace/mode/sass'
},
{
regex: /^lua$/,
replaceWith: 'ace/mode/lua'
},
{
regex: /^php$/,
replaceWith: 'ace/mode/php'
},
{
regex: /^ps1$/,
replaceWith: 'ace/mode/powershell'
},
{
regex: /^svg$/,
replaceWith: 'ace/mode/svg'
},
{
regex: /^sh$/,
replaceWith: 'ace/mode/sh'
},
{
regex: /^xml$/,
replaceWith: 'ace/mode/xml'
},
{
regex: /^ts$/,
replaceWith: 'ace/mode/typescript'
}
];
let fileName = '-< file_name >-.txt';
if (fileName.match('.')) {
// The pop method removes and returns the last element.
setMode(fileName
.split('.')
.pop()
.replace('ace/mode/', ''));
} else {
setMode('txt');
document
.querySelector('#file_warn')
.innerText = 'Warning: This is not a supported language';
}
function setMode (extension) {
// if the extension matches with the RegEx it will return the replaceWith
// property. else it will return the one it has. defaults to the extension.
// this runs for each element in extensionChanges.
let aceMode = extensionChanges.reduce((output, element) => {
return extension.match(element.regex)
? element.replaceWith
: output;
}, extension);
if (!aceMode.startsWith('ace/mode/')) {
document
.querySelector('#file_warn')
.innerText = 'Warning: This is not a supported language';
} else {
document
.querySelector('#file_warn')
.innerText = '';
console.log(aceMode || 'ace/mode/text');
editor.session.setMode(aceMode || 'ace/mode/text');
}
}
function save() {
let text = editor.session.getValue();
// Use AJAX or something
alert(text);
}
</script>
{% end %}

View File

@ -1,112 +0,0 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Looking at files in server -< server name >- (-< server path id >-){% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Looking at files in server -< server name >- (-< server path id >-)
<br />
<small>Path: -< location relative to server directory >-</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body">
<ul class="files-list list-group">
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="mx-1 btn btn-dark">Edit</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">File</span>
</span>
</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="mx-1 btn btn-dark">Open</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">Folder</span>
</span>
</span>
</li>
<!--
File:
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="mx-1 btn btn-dark">Edit</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">File</span>
</span>
</span>
</li>
Folder:
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="mx-1 btn btn-dark">Open</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">Folder</span>
</span>
</span>
</li>
-->
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
// empty for now
</script>
{% end %}

View File

@ -66,18 +66,54 @@
</td>
<td>{{ user.api_token }}</td>
<td>{{ user.allowed_servers}}</td>
<td><a href="/panel/edit_user?id={{user.id}}"><i class="fas fa-pencil-alt"></i></a></td>
<td>{{ [] }}</td>
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-server"></i> Roles</h4>
<div class="d-md-none">
<small>Can't see everything on mobile?<br /> Try scrolling the table sideways.</small>
</div>
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; Add New Role</a></div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr class="rounded">
<th>Role</th>
<th>Allowed Servers</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
{% for role in data['roles'] %}
<tr>
<td>{{ role.role_name }}</td>
<td>{{ [] }}</td>
<td><a href="/panel/edit_role?id={{role.role_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,159 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Edit Role{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
{% if data['new_role'] %}
<h4 class="page-title">
New Role
<br />
<small>RID: N/A</small>
</h4>
{% else %}
<h4 class="page-title">
Edit Role - {{ data['role']['role_name'] }}
<br />
<small>RID: {{ data['role']['role_id'] }}</small>
</h4>
{% end %}
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_name'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_name'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a>
</li>
</ul>
<div class="row">
<div class="col-md-6 col-sm-12">
{% if data['new_role'] %}
<form class="forms-sample" method="post" action="/panel/add_role">
{% else %}
<form class="forms-sample" method="post" action="/panel/edit_role">
{% end %}
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['role']['role_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="role_name">Role Name <small class="text-muted ml-1"> - What you wish to call this role</small> </label>
<input type="text" class="form-control" name="role_name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" >
</div>
<div class="form-group">
<label for="server_membership">Servers <small class="text-muted ml-1"> - servers this role is allowed to access </small> </label>
<div class="table-responsive">
<table class="table">
<thead>
<tr class="rounded">
<th>Server Name</th>
<th>Access?</th>
</tr>
</thead>
<tbody>
{% for server in data['servers_all'] %}
<tr>
<td>{{ server['server_name'] }}</td>
<td>
{% if server['server_id'] in data['role']['servers'] %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="1">
{% else %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" value="1">
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
<button type="submit" class="btn btn-success mr-2">Save</button>
<button type="reset" class="btn btn-light">Cancel</button>
</form>
</div>
<div class="col-md-6 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">Role Config Area</h4>
<p class="card-description"> Here is where you can change the configuration of your role</p>
<blockquote class="blockquote">
<p class="mb-0">
Created: {{ str(data['role']['created']) }}
<br />
Last updated: {{ str(data['role']['last_update']) }}
<br />
</p>
</blockquote>
</div>
</div>
<div class="text-center">
{% if data['new_role'] %}
<a class="btn btn-sm btn-danger disabled">Delete Role</a><br />
<small>You cannot delete something that does not yet exist</small>
{% else %}
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger">Delete Role</a>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$( document ).ready(function() {
console.log( "ready!" );
});
</script>
{% end %}

View File

@ -0,0 +1,235 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Edit User{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
{% if data['new_user'] %}
<h4 class="page-title">
New User
<br />
<small>UID: N/A</small>
</h4>
{% else %}
<h4 class="page-title">
Edit User - {{ data['user']['user_id'] }}
<br />
<small>UID: {{ data['user']['user_id'] }}</small>
</h4>
{% end %}
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_user?id={{ data['user']['username'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['username'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a>
</li>
</ul>
<div class="row">
<div class="col-md-6 col-sm-12">
{% if data['new_user'] %}
<form class="forms-sample" method="post" action="/panel/add_user">
{% else %}
<form class="forms-sample" method="post" action="/panel/edit_user">
{% end %}
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="username">User Name <small class="text-muted ml-1"> - What you wish to call this user</small> </label>
<input type="text" class="form-control" name="username" id="username" value="{{ data['user']['username'] }}" placeholder="User Name" >
</div>
<div class="form-group">
<label for="password0">Password <small class="text-muted ml-1"></small> </label>
<input type="password" class="form-control" name="password0" id="password0" value="" placeholder="Password" >
</div>
<div class="form-group">
<label for="password1">Repeat Password <small class="text-muted ml-1"></small> </label>
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
</div>
<div class="form-group">
<label for="role_membership">Roles <small class="text-muted ml-1"> - the roles this user is a member of</small> </label>
<div class="table-responsive">
<table class="table">
<thead>
<tr class="rounded">
<th>Role Name</th>
<th>Member?</th>
</tr>
</thead>
<tbody>
{% for role in data['roles_all'] %}
<tr>
<td>{{ role.role_name }}</td>
<td>
{% if role.role_id in data['user']['roles'] %}
<input type="checkbox" class="form-check-input" id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership" checked="" value="1">
{% else %}
<input type="checkbox" class="form-check-input" id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership" value="1">
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
<div class="form-group">
<label for="server_membership">Servers <small class="text-muted ml-1"> - servers this user is allowed to access </small> </label>
<div class="table-responsive">
<table class="table">
<thead>
<tr class="rounded">
<th>Server Name</th>
<th>Access?</th>
</tr>
</thead>
<tbody>
{% for server in data['servers_all'] %}
<tr>
<td>{{ server['server_name'] }}</td>
<td>
{% if server['server_id'] in data['user']['servers'] %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="1">
{% else %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" value="1">
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
{% if data['user']['enabled'] %}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">Enabled
{% else %}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" value="1">Enabled
{% end %}
</label>
<label for="regen_api" class="form-check-label ml-4 mb-4">
{% if data['new_user'] %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" checked="" value="1" disabled >Regenerate API Key
{% else %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" value="1">Regenerate API Key
{% end %}
</label>
<label for="superuser" class="form-check-label ml-4 mb-4">
{% if data['user']['superuser'] %}
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" checked="" value="1" disabled >Super User
{% else %}
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" value="1" disabled >Super User
{% end %}
</label>
</div>
<button type="submit" class="btn btn-success mr-2">Save</button>
<button type="reset" class="btn btn-light">Cancel</button>
</form>
</div>
<div class="col-md-6 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">User Config Area</h4>
<p class="card-description"> Here is where you can change the configuration of your user</p>
<blockquote class="blockquote">
<p class="mb-0">
Created: {{ str(data['user']['created']) }}
<br />
Last login: {{ str(data['user']['last_login']) }}
<br />
Last update: {{ str(data['user']['last_update']) }}
<br />
Last IP: {{ data['user']['last_ip'] }}
<br />
API Key: {{ data['user']['api_token'] }}
<br />
</p>
</blockquote>
</div>
</div>
<div class="text-center">
{% if data['new_user'] %}
<a class="btn btn-sm btn-danger disabled">Delete User</a><br />
<small>You cannot delete something that does not yet exist</small>
{% elif data['user']['superuser'] %}
<a class="btn btn-sm btn-danger disabled">Delete User</a><br />
<small>You cannot delete a superuser</small>
{% else %}
<a href="/panel/remove_user?id={{ data['user']['user_id'] }}" class="btn btn-sm btn-danger">Delete User</a>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$( document ).ready(function() {
console.log( "ready!" );
});
</script>
{% end %}

View File

@ -4,13 +4,14 @@
<div class="card-body pt-3 pb-3">
<div class="row">
<div class="col-sm-3 mr-2">
<b>Server Status:</b>
{% if data['server_stats'][0]['running'] %}
<span class="text-success">Online</span><br />
<b>Server Started:</b> {{ data['server_stats'][0]['started'] }}
<b>Server Status:</b> <span class="text-success">Online</span><br />
<b>Server Started:</b> <span id="started">{{ data['server_stats'][0]['started'] }} (Server Time)</span><br />
<b>Server Uptime:</b> <span id="uptime">Error Calculating</span>
{% else %}
<span class="text-danger">Offline</span><br />
<b>Server Started:</b> Not Started
<b>Server Status:</b> <span class="text-danger">Offline</span><br />
<b>Server Started:</b> <span class="text-danger">Offline</span><br />
<b>Server Uptime:</b> <span class="text-danger">Offline</span>
{% end %}
</div>
@ -40,4 +41,91 @@
</div>
</div>
</div>
</div>
<script src="/static/assets/vendors/moment/moment.min.js" type="text/javascript" charset="utf-8"></script>
<script>
function durationToHumanizedString (duration) {
duration._data.months += duration._data.years * 12;
// 30.45833333333 = average month length, calculate with (31+28.5+31+30+31+30+31+31+30+31+30+31) / 12
duration._data.days += duration._data.months * 30.45833333333;
duration._data.hours += duration._data.days * 24;
let obj = {
hours: Math.round(duration._data.hours),
minutes: duration._data.minutes,
seconds: duration._data.seconds
}
output = Object.entries(obj)
.map(([type, num]) => {
// make them strings
returnData = num + ' ' + type;
// remove the s in the end if the data is -1 or 1
if (num == -1 || num == 1)
returnData = returnData.slice(0, -1)
return returnData;
})
.map((v, i, a) => // example input: [1,2,3], output: "1, 2 and 3"
v + (i !== a.length - 1
? i !== a.length - 2
? ', '
: ' and '
: '')).join('');
return output;
}
document.body.onload = (() => {
console.log('calculateTime');
let uptime = document.querySelector('#uptime');
let started = document.querySelector('#started');
let startedUTC;
let startedLocal;
if (started != null) {
console.log('88', '{{ data['server_stats'][0]['started'] }}');
{% if data['server_stats'][0]['started'] != 'False' %}
startedUTC = '{{ (datetime.datetime.strptime(data['server_stats'][0]['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');
let browserUTCOffset = moment().utcOffset(); // This is in minutes
startedLocal = startedUTC.utcOffset(browserUTCOffset);
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
console.log('startedLocal', startedLocal);
console.log('startedLocalFormatted', startedLocalFormatted);
started.textContent = startedLocalFormatted
}
let nowServerTime = '{{ data['time'] }}';
let startedServerTime = '{{ data['server_stats'][0]['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()
.diff(startedLocal);
var diff = moment.duration(msdiff);
uptime.textContent = durationToHumanizedString(diff);
}, 1000)
}
}
});
</script>

View File

@ -112,6 +112,11 @@
<input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats'][0]['server_id']['server_port'] }}" step="1" max="65566" min="1" >
</div>
<div class="form-group">
<label for="logs_delete_after">Remove Old Logs After <small class="text-muted ml-1"> - How many days will a log file has to be old to get deleted (0 is off)</small> </label>
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats'][0]['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" >
</div>
<div class="form-check-flat">
<label for="auto_start" class="form-check-label ml-4 mb-4">
{% if data['server_stats'][0]['server_id']['auto_start'] %}

View File

@ -0,0 +1,692 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Server Details{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Server Details - {{ data['server_stats'][0]['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats'][0]['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
{% include "parts/details_stats.html %}
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Terminal</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Logs</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
<i class="fas fa-clock"></i>Schedule</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
<i class="fas fa-save"></i>Backup</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Files</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a>
</li>
</ul>
<div class="row">
<div class="col-md-6 col-sm-12">
<noscript>
The file manager does not work without JavaScript
</noscript>
<div id="files-tree-nav" class="overlay">
<!-- Button to close the overlay navigation -->
<a href="javascript:void(0)" class="closebtn" onclick="document.getElementById('files-tree-nav').style.height = '0%';">&times;</a>
<!-- Overlay content -->
<div id="files-tree-nav-content" class="overlay-content">
<a onclick="createFileE(event)" href="javascript:void(0)" id="createFile" href="#">Create file</a>
<a onclick="createDirE(event)" href="javascript:void(0)" id="createDir" href="#">Create directory</a>
<a onclick="renameItemE(event)" href="javascript:void(0)" id="renameItem" href="#">Rename</a>
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#">Delete</a>
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#">Delete</a>
</div>
</div>
<style>
/* The Overlay (background) */
.overlay {
/* Height & width depends on how you want to reveal the overlay (see JS below) */
height: 0;
width: 100vw;
position: fixed; /* Stay in place */
z-index: 1031; /* Sit on top */
left: 0;
top: 0;
background-color: rgb(0,0,0); /* Black fallback color */
background-color: rgba(0,0,0, 0.9); /* Black w/opacity */
overflow-x: hidden; /* Disable horizontal scroll */
transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */
}
/* Position the content inside the overlay */
.overlay-content {
position: relative;
top: 25%; /* 25% from the top */
width: 100%; /* 100% width */
text-align: center; /* Centered text/links */
margin-top: 30px; /* 30px top margin to avoid conflict with the close button on smaller screens */
}
/* The navigation links inside the overlay */
.overlay a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #818181;
display: block; /* Display block instead of inline */
transition: 0.3s; /* Transition effects on hover (color) */
}
/* When you mouse over the navigation links, change their color */
.overlay a:hover, .overlay a:focus {
color: #f1f1f1;
}
/* Position the close button (top right corner) */
.overlay .closebtn {
position: absolute;
top: 20px;
right: 45px;
font-size: 60px;
}
/* When the height of the screen is less than 450 pixels, change the font-size of the links and position the close button again, so they don't overlap */
@media screen and (max-height: 450px) {
.overlay a {font-size: 20px}
.overlay .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}
</style>
<ul class="tree-view">
<li>
<div class="tree-caret tree-ctx-item files-tree-title">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
Files
</div>
<ul class="tree-nested" id="files-tree">
<li>Error while getting files</li>
</ul>
</li>
</ul>
</div>
<style>
/* Remove default bullets */
.tree-view,
.tree-nested {
list-style-type: none;
margin: 0;
padding: 0;
margin-left: 10px;
}
/* Style the caret/arrow */
.tree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */
.tree-caret .fa-folder {
display: inline-block;
}
.tree-caret .fa-folder-open {
display: none;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.tree-caret-down .fa-folder {
display: none;
}
.tree-caret-down .fa-folder-open {
display: inline-block;
}
/* Hide the nested list */
.tree-nested {
display: none;
}
</style>
<div class="col-md-6 col-sm-12">
Editing file <span id="editingFile"></span>
<div id="editor" style="resize: both;">file_contents</div>
<div class="btn-group" role="group">
<button onclick="setKeyboard(event.target)" class="btn btn-primary" data-handler-name="null">Default</button>
<button onclick="setKeyboard(event.target)" class="btn btn-secondary" data-handler-name="ace/keyboard/vim">Vim</button>
<button onclick="setKeyboard(event.target)" class="btn btn-secondary" data-handler-name="ace/keyboard/emacs">Emacs</button>
<button onclick="setKeyboard(event.target)" class="btn btn-secondary" data-handler-name="ace/keyboard/sublime">Sublime</button>
</div>
<h3 id="file_warn"></h3>
<button class="btn btn-success" onclick="save()">Save</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script src="/static/assets/vendors/ace-builds/src-min/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
let editor = ace.edit('editor');
editor.setTheme('ace/theme/dracula');
editor.session.setUseSoftTabs(true);
// mouseup = css resize end
document.addEventListener("mouseup", function(e){
editor.resize();
});
let extensionChanges = [
{
regex: /^js$/,
replaceWith: 'ace/mode/javascript'
},
{
regex: /^py$/,
replaceWith: 'ace/mode/python'
},
{
regex: /^html$/,
replaceWith: 'ace/mode/html'
},
{
regex: /^yml$/,
replaceWith: 'ace/mode/yaml'
},
{
regex: /^yaml$/,
replaceWith: 'ace/mode/yaml'
},
{
regex: /^txt$/,
replaceWith: 'ace/mode/text'
},
{
regex: /^json$/,
replaceWith: 'ace/mode/json'
},
{
regex: /^java$/,
replaceWith: 'ace/mode/java'
},
{
regex: /^cpp$/,
replaceWith: 'ace/mode/c_cpp'
},
{
regex: /^css$/,
replaceWith: 'ace/mode/css'
},
{
regex: /^scss$/,
replaceWith: 'ace/mode/scss'
},
{
regex: /^sass$/,
replaceWith: 'ace/mode/sass'
},
{
regex: /^lua$/,
replaceWith: 'ace/mode/lua'
},
{
regex: /^php$/,
replaceWith: 'ace/mode/php'
},
{
regex: /^ps1$/,
replaceWith: 'ace/mode/powershell'
},
{
regex: /^svg$/,
replaceWith: 'ace/mode/svg'
},
{
regex: /^sh$/,
replaceWith: 'ace/mode/sh'
},
{
regex: /^xml$/,
replaceWith: 'ace/mode/xml'
},
{
regex: /^ts$/,
replaceWith: 'ace/mode/typescript'
}
];
var editorEnabled = false;
var filePath = '';
function clickOnFile(event) {
editorEnabled = true;
filePath = event.target.getAttribute('data-path');
setFileName(event.target.innerText);
$.ajax({
type: 'GET',
url: '/ajax/get_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}&file_path=' + encodeURIComponent(filePath),
dataType: 'text',
success: function (data) {
console.log('Got File Contents From Server');
editor.session.setValue(data);
},
});
}
function setFileName(name) {
let fileName = name || 'default.txt';
document.getElementById('editingFile').innerText = fileName;
if (fileName.match('.')) {
// The pop method removes and returns the last element.
setMode(fileName
.split('.')
.pop()
.replace('ace/mode/', ''));
} else {
setMode('txt');
document
.querySelector('#file_warn')
.innerText = 'Warning: This is not a supported language';
}
}
setFileName();
function setMode (extension) {
// if the extension matches with the RegEx it will return the replaceWith
// property. else it will return the one it has. defaults to the extension.
// this runs for each element in extensionChanges.
let aceMode = extensionChanges.reduce((output, element) => {
return extension.match(element.regex)
? element.replaceWith
: output;
}, extension);
if (!aceMode.startsWith('ace/mode/')) {
document
.querySelector('#file_warn')
.innerText = 'Warning: This is not a supported language';
} else {
document
.querySelector('#file_warn')
.innerText = '';
console.log(aceMode || 'ace/mode/text');
editor.session.setMode(aceMode || 'ace/mode/text');
}
}
function save() {
let text = editor.session.getValue();
var token = getCookie("_xsrf")
$.ajax({
type: "PUT",
headers: {'X-XSRFToken': token},
url: '/ajax/save_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
file_contents: text,
file_path: filePath
},
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
function createFile(parent, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/create_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
file_parent: parent,
file_name: name
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function createDir(parent, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/create_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
dir_parent: parent,
dir_name: name
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function renameItem(path, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "PUT",
headers: {'X-XSRFToken': token},
url: '/ajax/rename_item?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
item_path: path,
new_item_name: name
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function deleteFile(path, callback) {
console.log('Deleting: ' + path)
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
file_path: path
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function deleteDir(path, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
dir_path: path
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function getTreeView() {
$.ajax({
type: "GET",
url: '/ajax/get_tree?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
dataType: 'text',
success: function(data){
console.log("got response:");
console.log(data);
dataArr = data.split('\n');
serverDir = dataArr.shift(); // Remove & return first element (server directory)
text = dataArr.join('\n');
document.getElementById('files-tree').innerHTML = text;
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
setTimeout(function () {setTreeViewContext()}, 1000);
var toggler = document.getElementsByClassName("tree-caret");
var i;
for (i = 0; i < toggler.length; i++) {
if (toggler[i].classList.contains('files-tree-title')) continue;
toggler[i].addEventListener("click", function caretListener() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
this.classList.toggle("tree-caret-down");
});
}
},
});
}
function setTreeViewContext() {
var treeItems = document.getElementsByClassName('tree-ctx-item');
for (var i = 0; i < treeItems.length; i++) {
var treeItem = treeItems[i];
treeItem.addEventListener('contextmenu', function contextListener(event) {
event.preventDefault();
var ctxmenuPath = event.target.getAttribute('data-path');
var ctxmenuName = event.target.getAttribute('data-name');
if (!ctxmenuPath) {
console.log({ 'event.target': event.target, ctxmenuPath });
return;
}
$('#renameItem').show();
var isDir = event.target.classList.contains('tree-folder');
$('#createFile').toggle(isDir);
$('#createDir').toggle(isDir);
$('#deleteDir').toggle(isDir);
var isFile = event.target.classList.contains('tree-file');
$('#deleteFile').toggle(isFile);
console.log({ 'event.target': event.target, isDir, isFile });
if(event.target.classList.contains('files-tree-title')) {
$('#createFile').show();
$('#createDir').show();
$('#renameItem').hide();
$('#deleteDir').hide();
$('#deleteFile').hide();
}
document.getElementById('files-tree-nav-content')
.setAttribute('data-path', ctxmenuPath);
document.getElementById('files-tree-nav-content')
.setAttribute('data-name', ctxmenuName);
document.getElementById("files-tree-nav").style.height = "100%";
})
}
}
function createFileE(event) {
bootbox.prompt('What name do you want for the new file?', function(result) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
if (!result) return;
createFile(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
})
}
function createDirE(event) {
bootbox.prompt('What name do you want for the new directory?', function(result) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
if (!result) return;
createDir(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
})
}
function renameItemE(event) {
bootbox.prompt('What should the new name be?', function(result) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
if (!result) return;
renameItem(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
})
}
function deleteFileE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
bootbox.confirm({
size: "",
title: "Are you sure you want to delete " + name + "?",
closeButton: false,
message: "You are deleting \"" + path + "\"!<br/><br/>This action will be irreversible and it'll be lost forever!",
buttons: {
confirm: {
label: 'Yes, I understand the consequences',
className: 'btn-danger'
},
cancel: {
label: 'No',
className: 'btn-link'
}
},
callback: function(result) {
if (!result) return;
deleteFile(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
}
});
}
function deleteDirE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
bootbox.confirm({
size: "",
title: "Are you sure you want to delete " + name + "?",
closeButton: false,
message: "You are deleting \"" + path + "\"!<br/><br/>This action will be irreversible and it'll be lost forever!",
buttons: {
confirm: {
label: 'Yes, I understand the consequences',
className: 'btn-danger'
},
cancel: {
label: 'No',
className: 'btn-link'
}
},
callback: function(result) {
if (!result) return;
deleteDir(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
}
});
}
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();
setTreeViewContext();
function setKeyboard(target) {
var handlerName = target.getAttribute('data-handler-name');
if (handlerName == 'null') handlerName = null;
editor.setKeyboardHandler(handlerName);
var nodes = target.parentNode.querySelectorAll("[data-handler-name]");
for (var i = 0; i < nodes.length; i++) {
nodes[i].classList.remove('btn-primary');
nodes[i].classList.add('btn-secondary');
}
target.classList.remove('btn-secondary');
target.classList.add('btn-primary');
}
</script>
{% end %}

0
app/frontend/templates/public/404.html Executable file → Normal file
View File

0
app/frontend/templates/public/error.html Executable file → Normal file
View File

0
app/frontend/templates/public/login.html Executable file → Normal file
View File

View File

@ -140,7 +140,7 @@
</div>
</div>
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg()">Import My Server!</button>
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">Import My Server!</button>
<button type="reset" class="btn btn-danger mr-2">Reset Form</button>
</form>
@ -216,7 +216,7 @@
</div>
</div>
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg()">Import My Server!</button>
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">Import My Server!</button>
<button type="reset" class="btn btn-danger mr-2">Reset Form</button>
</div>
</div>
@ -239,10 +239,10 @@
});
});
function wait_msg(){
function wait_msg(importing){
bootbox.alert({
title: 'Downloading Server...',
message: '<i class="fas fa-cloud-download"></i> Please be patient as we download the server'
title: importing ? 'Importing Server...' : 'Downloading Server...',
message: '<i class="fas fa-cloud-download"></i> Please be patient as we ' + (importing ? 'import' : 'download') + ' the server'
});
}

0
app/frontend/templates/setup/setup1.html Executable file → Normal file
View File

View File

@ -44,7 +44,9 @@ def setup_logging(debug=False):
logging_config = json.load(f)
if debug:
logging_config['loggers']['']['level'] = 'DEBUG'
logging.config.dictConfig(logging_config)
else:
logging.basicConfig(level=logging.DEBUG)
logging.warning("Unable to read logging config from {}".format(logging_config_file))
@ -74,6 +76,7 @@ if __name__ == '__main__':
# setting up the logger object
logger = logging.getLogger(__name__)
print("Logging set to: {} ".format(logger.level))
# print our pretty start message
do_intro()