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: class ServerJars:
def __init__(self):
self.base_url = "https://serverjars.com"
def _get_api_result(self, call_url: str): def _get_api_result(self, call_url: str):
base_url = "https://serverjars.com" full_url = "{base}{call_url}".format(base=self.base_url, call_url=call_url)
full_url = "{base}{call_url}".format(base=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 {} return {}
try: try:
@ -66,18 +72,21 @@ class ServerJars:
data = self._read_cache() data = self._read_cache()
return data.get('servers') return data.get('servers')
@staticmethod def _check_api_alive(self):
def _check_api_alive():
logger.info("Checking serverjars.com API status") logger.info("Checking serverjars.com API status")
check_url = "https://serverjars.com/api/fetchTypes" check_url = "{base}/api/fetchTypes".format(base=self.base_url)
r = requests.get(check_url, timeout=2) try:
r = requests.get(check_url, timeout=2)
if r.status_code in [200, 201]: if r.status_code in [200, 201]:
logger.info("Serverjars.com API is alive") logger.info("Serverjars.com API is alive")
return True 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 return False
def refresh_cache(self): def refresh_cache(self):
@ -141,12 +150,11 @@ class ServerJars:
response = self._get_api_result(url) response = self._get_api_result(url)
return response return response
@staticmethod def download_jar(self, server, version, path):
def download_jar(server, version, path): fetch_url = "{base}/api/fetchJar/{server}/{version}".format(base=self.base_url, server=server, version=version)
base_url = "https://serverjars.com/api/fetchJar/{server}/{version}".format(server=server, version=version)
# open a file stream # 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: try:
with open(path, 'wb') as output: with open(path, 'wb') as output:
shutil.copyfileobj(r.raw, output) shutil.copyfileobj(r.raw, output)

View File

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

View File

@ -1,6 +1,6 @@
import datetime import datetime
import logging import logging
from sys import modules import sys
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -11,18 +11,20 @@ try:
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
logging.critical("Import Error: Unable to load {} module".format(e, e.name)) logging.critical("Import Error: Unable to load {} module".format(e, e.name))
print("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: class Console:
def __init__(self): def __init__(self):
if 'colorama' in modules: if 'colorama' in sys.modules:
init() init()
@staticmethod @staticmethod
def do_print(message, color): 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)) print(colored(message, color))
else: else:
print(message) print(message)

View File

@ -51,7 +51,7 @@ class Controller:
continue continue
# if this server path no longer exists - let's warn and bomb out # 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'], logger.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'],
s['path'])) s['path']))
@ -212,7 +212,9 @@ class Controller:
logger.error("Unable to create required server files due to :{}".format(e)) logger.error("Unable to create required server files due to :{}".format(e))
return False 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_log_file = "{}/logs/latest.log".format(server_dir)
server_stop = "stop" server_stop = "stop"
@ -224,7 +226,7 @@ class Controller:
@staticmethod @staticmethod
def verify_jar_server( server_path: str, server_jar: str): 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)) jar_check = helper.check_file_exists(os.path.join(server_path, server_jar))
if not path_check or not jar_check: if not path_check or not jar_check:
return False return False
@ -245,7 +247,9 @@ class Controller:
dir_util.copy_tree(server_path, new_server_dir) dir_util.copy_tree(server_path, new_server_dir)
full_jar_path = os.path.join(new_server_dir, server_jar) 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_log_file = "{}/logs/latest.log".format(new_server_dir)
server_stop = "stop" server_stop = "stop"
@ -264,7 +268,10 @@ class Controller:
return "false" return "false"
full_jar_path = os.path.join(new_server_dir, server_jar) 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_log_file = "{}/logs/latest.log".format(new_server_dir)
server_stop = "stop" server_stop = "stop"

View File

@ -9,6 +9,7 @@ import base64
import socket import socket
import random import random
import logging import logging
import html
from datetime import datetime from datetime import datetime
from socket import gethostname from socket import gethostname
@ -45,6 +46,10 @@ class Helpers:
self.passhasher = PasswordHasher() self.passhasher = PasswordHasher()
self.exiting = False 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): def check_file_perms(self, path):
try: try:
fp = open(path, "r").close() fp = open(path, "r").close()
@ -61,8 +66,27 @@ class Helpers:
return True return True
else: else:
return False return False
logger.error("{} does not exits".format(file)) logger.error("{} does not exist".format(file))
return False 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): def get_setting(self, key, default_return=False):
@ -280,7 +304,7 @@ class Helpers:
return "%.1f%s%s" % (num, 'Y', suffix) return "%.1f%s%s" % (num, 'Y', suffix)
@staticmethod @staticmethod
def check_path_exits(path: str): def check_path_exists(path: str):
logger.debug('Looking for path: {}'.format(path)) logger.debug('Looking for path: {}'.format(path))
if os.path.exists(path): if os.path.exists(path):
@ -463,6 +487,35 @@ class Helpers:
return data 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() 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() user_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
last_login = DateTimeField(default=datetime.datetime.now) last_login = DateTimeField(default=datetime.datetime.now)
last_update = DateTimeField(default=datetime.datetime.now)
last_ip = CharField(default="") last_ip = CharField(default="")
username = CharField(default="") username = CharField(default="", unique=True, index=True)
password = CharField(default="") password = CharField(default="")
enabled = BooleanField(default=True) enabled = BooleanField(default=True)
api_token = CharField(default="") superuser = BooleanField(default=False)
allowed_servers = CharField(default="[]") api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
class Meta: class Meta:
table_name = "users" 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): class Audit_Log(BaseModel):
audit_id = AutoField() audit_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
user_name = CharField(default="") user_name = CharField(default="")
user_id = IntegerField(default=0) user_id = IntegerField(default=0, index=True)
source_ip = CharField(default='127.0.0.1') 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='') log_msg = TextField(default='')
class Host_Stats(BaseModel): class Host_Stats(BaseModel):
time = DateTimeField(default=datetime.datetime.now) time = DateTimeField(default=datetime.datetime.now, index=True)
boot_time = CharField(default="") boot_time = CharField(default="")
cpu_usage = FloatField(default=0) cpu_usage = FloatField(default=0)
cpu_cores = IntegerField(default=0) cpu_cores = IntegerField(default=0)
@ -74,8 +94,8 @@ class Host_Stats(BaseModel):
class Servers(BaseModel): class Servers(BaseModel):
server_id = AutoField() server_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
server_uuid = CharField(default="") server_uuid = CharField(default="", index=True)
server_name = CharField(default="Server") server_name = CharField(default="Server", index=True)
path = CharField(default="") path = CharField(default="")
executable = CharField(default="") executable = CharField(default="")
log_path = CharField(default="") log_path = CharField(default="")
@ -86,15 +106,34 @@ class Servers(BaseModel):
stop_command = CharField(default="stop") stop_command = CharField(default="stop")
server_ip = CharField(default="127.0.0.1") server_ip = CharField(default="127.0.0.1")
server_port = IntegerField(default=25565) server_port = IntegerField(default=25565)
logs_delete_after = IntegerField(default=0)
class Meta: class Meta:
table_name = "servers" 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): class Server_Stats(BaseModel):
stats_id = AutoField() stats_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server') server_id = ForeignKeyField(Servers, backref='server', index=True)
started = CharField(default="") started = CharField(default="")
running = BooleanField(default=False) running = BooleanField(default=False)
cpu = FloatField(default=0) cpu = FloatField(default=0)
@ -118,8 +157,8 @@ class Server_Stats(BaseModel):
class Commands(BaseModel): class Commands(BaseModel):
command_id = AutoField() command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server') server_id = ForeignKeyField(Servers, backref='server', index=True)
user = ForeignKeyField(Users, backref='user') user = ForeignKeyField(Users, backref='user', index=True)
source_ip = CharField(default='127.0.0.1') source_ip = CharField(default='127.0.0.1')
command = CharField(default='') command = CharField(default='')
executed = BooleanField(default=False) executed = BooleanField(default=False)
@ -130,7 +169,7 @@ class Commands(BaseModel):
class Webhooks(BaseModel): class Webhooks(BaseModel):
id = AutoField() id = AutoField()
name = CharField(max_length=64, unique=True) name = CharField(max_length=64, unique=True, index=True)
method = CharField(default="POST") method = CharField(default="POST")
url = CharField(unique=True) url = CharField(unique=True)
event = CharField(default="") event = CharField(default="")
@ -144,7 +183,7 @@ class Backups(BaseModel):
directories = CharField() directories = CharField()
storage_location = CharField() storage_location = CharField()
max_backups = IntegerField() max_backups = IntegerField()
server_id = IntegerField() server_id = IntegerField(index=True)
class Meta: class Meta:
table_name = 'backups' table_name = 'backups'
@ -158,9 +197,13 @@ class db_builder:
database.create_tables([ database.create_tables([
Backups, Backups,
Users, Users,
Roles,
User_Roles,
Host_Stats, Host_Stats,
Webhooks, Webhooks,
Servers, Servers,
User_Servers,
Role_Servers,
Server_Stats, Server_Stats,
Commands, Commands,
Audit_Log Audit_Log
@ -174,16 +217,18 @@ class db_builder:
username = default_data.get("username", 'admin') username = default_data.get("username", 'admin')
password = default_data.get("password", 'crafty') password = default_data.get("password", 'crafty')
api_token = helper.random_string_generator(32) #api_token = helper.random_string_generator(32)
#
Users.insert({ #Users.insert({
Users.username: username.lower(), # Users.username: username.lower(),
Users.password: helper.encode_pass(password), # Users.password: helper.encode_pass(password),
Users.api_token: api_token, # Users.api_token: api_token,
Users.enabled: True # Users.enabled: True,
}).execute() # 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 @staticmethod
def is_fresh_install(): def is_fresh_install():
@ -197,7 +242,8 @@ class db_builder:
class db_shortcuts: class db_shortcuts:
def return_rows(self, query): @staticmethod
def return_rows(query):
rows = [] rows = []
try: try:
@ -210,7 +256,8 @@ class db_shortcuts:
return rows return rows
def get_server_data_by_id(self, server_id): @staticmethod
def get_server_data_by_id(server_id):
try: try:
query = Servers.get_by_id(server_id) query = Servers.get_by_id(server_id)
except DoesNotExist: except DoesNotExist:
@ -218,25 +265,29 @@ class db_shortcuts:
return model_to_dict(query) return model_to_dict(query)
def get_all_defined_servers(self): @staticmethod
def get_all_defined_servers():
query = Servers.select() query = Servers.select()
return self.return_rows(query) return db_helper.return_rows(query)
def get_all_servers_stats(self): @staticmethod
servers = self.get_all_defined_servers() def get_all_servers_stats():
servers = db_helper.get_all_defined_servers()
server_data = [] server_data = []
for s in servers: 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) 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 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) 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): @staticmethod
if not self.get_server_data_by_id(server_id): def server_id_exists(server_id):
if not db_helper.get_server_data_by_id(server_id):
return False return False
return True return True
@ -245,13 +296,197 @@ class db_shortcuts:
query = Host_Stats.select().order_by(Host_Stats.id.desc()).get() query = Host_Stats.select().order_by(Host_Stats.id.desc()).get()
return model_to_dict(query) 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() query = Users.select()
return query 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) query = Commands.select().where(Commands.executed == 0)
return self.return_rows(query) return db_helper.return_rows(query)
@staticmethod @staticmethod
def get_server_friendly_name(server_id): def get_server_friendly_name(server_id):
@ -275,11 +510,13 @@ class db_shortcuts:
Commands.command: command Commands.command: command
}).execute() }).execute()
def get_actity_log(self): @staticmethod
def get_actity_log():
q = Audit_Log.select() 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] data = [model_to_dict(row) for row in model]
return data return data

View File

@ -97,7 +97,7 @@ class Server:
console.critical("Server executable path: {} does not seem to exist".format(full_path)) console.critical("Server executable path: {} does not seem to exist".format(full_path))
helper.do_exit() 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)) 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)) console.critical("Server path: {} does not seem to exits".format(self.server_path))
helper.do_exit() 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.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 = threading.Thread(target=self.command_watcher, daemon=True, name="command_watcher")
self.command_thread.start() self.command_thread.start()

View File

@ -3,6 +3,8 @@ import logging
import tornado.web import tornado.web
import tornado.escape import tornado.escape
import bleach import bleach
import os
import shutil
from app.classes.shared.console import console from app.classes.shared.console import console
from app.classes.shared.models import Users, installer from app.classes.shared.models import Users, installer
@ -55,7 +57,7 @@ class AjaxHandler(BaseHandler):
self.redirect("/panel/error?error=Server ID Not Found") self.redirect("/panel/error?error=Server ID Not Found")
if server_data['log_path']: 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: if full_log:
log_lines = helper.get_setting('max_log_lines') log_lines = helper.get_setting('max_log_lines')
@ -79,7 +81,57 @@ class AjaxHandler(BaseHandler):
page_data['notify_data'] = data page_data['notify_data'] = data
self.render_page('ajax/notify.html', page_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): def post(self, page):
user_data = json.loads(self.get_secure_cookie("user_data")) user_data = json.loads(self.get_secure_cookie("user_data"))
error = bleach.clean(self.get_argument('error', "WTF Error!")) error = bleach.clean(self.get_argument('error', "WTF Error!"))
@ -90,11 +142,12 @@ class AjaxHandler(BaseHandler):
} }
if page == "send_command": if page == "send_command":
command = bleach.clean(self.get_body_argument('command', default=None, strip=True)) command = self.get_body_argument('command', default=None, strip=True)
server_id = bleach.clean(self.get_argument('id')) server_id = self.get_argument('id')
if server_id is None: if server_id is None:
logger.warning("Server ID not found in send_command ajax call") 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) srv_obj = controller.get_server_obj(server_id)
@ -102,3 +155,192 @@ class AjaxHandler(BaseHandler):
if srv_obj.check_running(): if srv_obj.check_running():
srv_obj.send_command(command) 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.web
import tornado.escape import tornado.escape
import bleach import bleach
import time
import datetime
from app.classes.shared.console import console from app.classes.shared.console import console
from app.classes.shared.models import Users, installer from app.classes.shared.models import Users, installer
@ -23,6 +25,9 @@ class PanelHandler(BaseHandler):
template = "panel/denied.html" 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() defined_servers = controller.list_defined_servers()
page_data = { page_data = {
@ -38,7 +43,8 @@ class PanelHandler(BaseHandler):
'menu_servers': defined_servers, 'menu_servers': defined_servers,
'hosts_data': db_helper.get_latest_hosts_stats(), 'hosts_data': db_helper.get_latest_hosts_stats(),
'show_contribute': helper.get_setting("show_contribute_link", True), '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 # if no servers defined, let's go to the build server area
@ -59,12 +65,6 @@ class PanelHandler(BaseHandler):
elif page == 'contribute': elif page == 'contribute':
template = "panel/contribute.html" 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": elif page == "remove_server":
server_id = self.get_argument('id', None) server_id = self.get_argument('id', None)
server_data = controller.get_server_data(server_id) server_data = controller.get_server_data(server_id)
@ -106,10 +106,12 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID") self.redirect("/panel/error?error=Invalid Server ID")
return False return False
valid_subpages = ['term', 'logs', 'config'] valid_subpages = ['term', 'logs', 'config', 'files']
if subpage not in valid_subpages: if subpage not in valid_subpages:
logger.debug('not a valid subpage')
subpage = 'term' subpage = 'term'
logger.debug('Subpage: "{}"'.format(subpage))
# server_data isn't needed since the server_stats also pulls server data # 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) # page_data['server_data'] = db_helper.get_server_data_by_id(server_id)
@ -120,9 +122,150 @@ class PanelHandler(BaseHandler):
elif page == 'panel_config': elif page == 'panel_config':
page_data['users'] = db_helper.get_all_users() 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" 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": elif page == "activity_logs":
page_data['audit_logs'] = db_helper.get_actity_log() page_data['audit_logs'] = db_helper.get_actity_log()
@ -130,7 +273,9 @@ class PanelHandler(BaseHandler):
self.render( self.render(
template, template,
data=page_data data=page_data,
time=time,
utc_offset=(time.timezone * -1 / 60 / 60),
) )
@tornado.web.authenticated @tornado.web.authenticated
@ -149,9 +294,16 @@ class PanelHandler(BaseHandler):
server_port = self.get_argument('server_port', None) server_port = self.get_argument('server_port', None)
auto_start = int(float(self.get_argument('auto_start', '0'))) auto_start = int(float(self.get_argument('auto_start', '0')))
crash_detection = int(float(self.get_argument('crash_detection', '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) 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") self.redirect("/panel/error?error=Invalid Server ID")
return False return False
else: else:
@ -174,15 +326,223 @@ class PanelHandler(BaseHandler):
Servers.server_port: server_port, Servers.server_port: server_port,
Servers.auto_start: auto_start, Servers.auto_start: auto_start,
Servers.crash_detection: crash_detection, Servers.crash_detection: crash_detection,
Servers.logs_delete_after: logs_delete_after,
}).where(Servers.server_id == server_id).execute() }).where(Servers.server_id == server_id).execute()
controller.refresh_server_settings(server_id) 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'], db_helper.add_to_audit_log(user_data['user_id'],
"Edited server {} named {}".format(server_id, server_name), "Edited server {} named {}".format(server_id, server_name),
server_id, server_id,
self.get_remote_ip()) self.get_remote_ip())
self.redirect("/panel/server_detail?id={}&subpage=config".format(server_id)) 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 = { cookie_data = {
"username": user_data.username, "username": user_data.username,
"user_id": user_data.user_id, "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)) self.set_secure_cookie('user_data', json.dumps(cookie_data))

View File

@ -1,6 +1,8 @@
import sys import sys
import json import json
import logging import logging
import os
import shutil
from app.classes.shared.console import console from app.classes.shared.console import console
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
@ -75,6 +77,64 @@ class ServerHandler(BaseHandler):
command = bleach.clean(self.get_argument("command", None)) command = bleach.clean(self.get_argument("command", None))
if server_id is not 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) db_helper.send_command(user_data['user_id'], server_id, self.get_remote_ip(), command)
if page == "step1": if page == "step1":

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -2,7 +2,7 @@
<footer class="footer"> <footer class="footer">
<div class="container-fluid "> <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 class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">Version: {{ data['version_data'] }}
</span> </span>
</div> </div>

View File

@ -64,6 +64,14 @@
</li> </li>
{% end %} {% 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> </ul>
</nav> </nav>

View File

@ -375,6 +375,47 @@
</div> </div>
</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> <!-- end user row-->
</div> </div>
@ -471,6 +512,7 @@
</div>
</div> </div>
<!-- content-wrapper ends --> <!-- 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; <a class="restart_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-sync"></i></a> &nbsp;
{% else %} {% 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="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 %} {% end %}
</td> </td>
@ -217,7 +218,7 @@
<script> <script>
function send_command (server_id, command){ function send_command (server_id, command){
<!-- this getCookie function is in base.html--> /* this getCookie function is in base.html */
var token = getCookie("_xsrf"); var token = getCookie("_xsrf");
$.ajax({ $.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> </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>
<td>{{ user.api_token }}</td> <td>{{ user.api_token }}</td>
<td>{{ user.allowed_servers}}</td> <td>{{ [] }}</td>
<td><a href="/panel/edit_user?id={{user.id}}"><i class="fas fa-pencil-alt"></i></a></td> <td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr> </tr>
{% end %} {% end %}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
</div> </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> </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="card-body pt-3 pb-3">
<div class="row"> <div class="row">
<div class="col-sm-3 mr-2"> <div class="col-sm-3 mr-2">
<b>Server Status:</b>
{% if data['server_stats'][0]['running'] %} {% if data['server_stats'][0]['running'] %}
<span class="text-success">Online</span><br /> <b>Server Status:</b> <span class="text-success">Online</span><br />
<b>Server Started:</b> {{ data['server_stats'][0]['started'] }} <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 %} {% else %}
<span class="text-danger">Offline</span><br /> <b>Server Status:</b> <span class="text-danger">Offline</span><br />
<b>Server Started:</b> Not Started <b>Server Started:</b> <span class="text-danger">Offline</span><br />
<b>Server Uptime:</b> <span class="text-danger">Offline</span>
{% end %} {% end %}
</div> </div>
@ -40,4 +41,91 @@
</div> </div>
</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" > <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>
<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"> <div class="form-check-flat">
<label for="auto_start" class="form-check-label ml-4 mb-4"> <label for="auto_start" class="form-check-label ml-4 mb-4">
{% if data['server_stats'][0]['server_id']['auto_start'] %} {% 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>
</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> <button type="reset" class="btn btn-danger mr-2">Reset Form</button>
</form> </form>
@ -216,7 +216,7 @@
</div> </div>
</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> <button type="reset" class="btn btn-danger mr-2">Reset Form</button>
</div> </div>
</div> </div>
@ -239,10 +239,10 @@
}); });
}); });
function wait_msg(){ function wait_msg(importing){
bootbox.alert({ bootbox.alert({
title: 'Downloading Server...', title: importing ? 'Importing Server...' : 'Downloading Server...',
message: '<i class="fas fa-cloud-download"></i> Please be patient as we download the 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) logging_config = json.load(f)
if debug: if debug:
logging_config['loggers']['']['level'] = 'DEBUG' logging_config['loggers']['']['level'] = 'DEBUG'
logging.config.dictConfig(logging_config) logging.config.dictConfig(logging_config)
else: else:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logging.warning("Unable to read logging config from {}".format(logging_config_file)) logging.warning("Unable to read logging config from {}".format(logging_config_file))
@ -74,6 +76,7 @@ if __name__ == '__main__':
# setting up the logger object # setting up the logger object
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
print("Logging set to: {} ".format(logger.level))
# print our pretty start message # print our pretty start message
do_intro() do_intro()