Merge branch 'backups-and-stuff' into dev

This commit is contained in:
computergeek125 2021-03-21 23:32:48 -05:00
commit 506c9bb3c3
31 changed files with 1267 additions and 623 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ venv.bak/
.idea/
servers/
backups/
session.lock
.header
default.json

View File

@ -9,7 +9,6 @@ from datetime import datetime
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.shared.models import Servers
# from app.classes.shared.controller import controller
from app.classes.minecraft.server_props import ServerProps
logger = logging.getLogger(__name__)

View File

@ -8,7 +8,6 @@ import datetime
from app.classes.shared.helpers import helper
from app.classes.minecraft.mc_ping import ping
from app.classes.shared.controller import controller
from app.classes.shared.models import db_helper
from app.classes.shared.models import Host_Stats, Server_Stats
@ -17,6 +16,9 @@ logger = logging.getLogger(__name__)
class Stats:
def __init__(self, controller):
self.controller = controller
def get_node_stats(self):
boot_time = datetime.datetime.fromtimestamp(psutil.boot_time())
data = {}
@ -184,7 +186,7 @@ class Stats:
server_stats_list = []
server_stats = {}
servers = controller.servers_list
servers = self.controller.servers_list
logger.info("Getting Stats for all servers...")
@ -211,7 +213,7 @@ class Stats:
internal_ip = server_data.get('server-ip', "127.0.0.1")
server_port = server_settings.get('server-port', "25565")
logger.debug("Pinging {} on port {}".format(internal_ip, server_port))
logger.debug("Pinging server '{}' on {}:{}".format(s.get('server_name', "ID#{}".format(server_id)), internal_ip, server_port))
int_mc_ping = ping(internal_ip, int(server_port))
int_data = False
@ -288,6 +290,4 @@ class Stats:
last_week = now.day - max_age
Host_Stats.delete().where(Host_Stats.time < last_week).execute()
Server_Stats.delete().where(Server_Stats.created < last_week).execute()
stats = Stats()
Server_Stats.delete().where(Server_Stats.created < last_week).execute()

View File

@ -20,7 +20,11 @@ except ModuleNotFoundError as e:
sys.exit(1)
class MainPrompt(cmd.Cmd):
class MainPrompt(cmd.Cmd, object):
def __init__(self, tasks_manager):
super().__init__()
self.tasks_manager = tasks_manager
# overrides the default Prompt
prompt = "Crafty Controller v{} > ".format(helper.get_version_string())

View File

@ -15,6 +15,7 @@ from app.classes.shared.models import db_helper, Servers, User_Servers
from app.classes.shared.server import Server
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
logger = logging.getLogger(__name__)
@ -23,6 +24,7 @@ class Controller:
def __init__(self):
self.servers_list = []
self.stats = Stats(self)
def check_server_loaded(self, server_id_to_check: int):
@ -72,7 +74,7 @@ class Controller:
temp_server_dict = {
'server_id': s.get('server_id'),
'server_data_obj': s,
'server_obj': Server(),
'server_obj': Server(self.stats),
'server_settings': settings.props
}
@ -94,7 +96,6 @@ class Controller:
server_obj.reload_server_settings()
def get_server_obj(self, server_id):
for s in self.servers_list:
if int(s['server_id']) == int(server_id):
return s['server_obj']
@ -196,12 +197,14 @@ class Controller:
def create_jar_server(self, server: str, version: str, name: str, min_mem: int, max_mem: int, port: int):
server_id = helper.create_uuid()
server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
server_file = "{server}-{version}.jar".format(server=server, version=version)
full_jar_path = os.path.join(server_dir, server_file)
# make the dir - perhaps a UUID?
helper.ensure_dir_exists(server_dir)
helper.ensure_dir_exists(backup_path)
try:
# do a eula.txt
@ -227,7 +230,7 @@ class Controller:
# download the jar
server_jar_obj.download_jar(server, version, full_jar_path)
new_id = self.register_server(name, server_id, server_dir, server_command, server_file, server_log_file, server_stop)
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop)
return new_id
@staticmethod
@ -248,8 +251,10 @@ class Controller:
def import_jar_server(self, server_name: str, server_path: str, server_jar: str, min_mem: int, max_mem: int, port: int):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
dir_util.copy_tree(server_path, new_server_dir)
full_jar_path = os.path.join(new_server_dir, server_jar)
@ -259,15 +264,18 @@ class Controller:
server_log_file = "{}/logs/latest.log".format(new_server_dir)
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, server_command, server_jar,
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
server_log_file, server_stop, port)
return new_id
def import_zip_server(self, server_name: str, zip_path: str, server_jar: str, min_mem: int, max_mem: int, port: int):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.check_file_perms(zip_path):
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(new_server_dir)
else:
@ -281,11 +289,11 @@ class Controller:
server_log_file = "{}/logs/latest.log".format(new_server_dir)
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, server_command, server_jar,
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
server_log_file, server_stop, port)
return new_id
def register_server(self, name: str, server_id: str, server_dir: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
def register_server(self, name: str, server_id: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
# put data in the db
new_id = Servers.insert({
Servers.server_name: name,
@ -298,7 +306,8 @@ class Controller:
Servers.crash_detection: False,
Servers.log_path: server_log_file,
Servers.server_port: server_port,
Servers.stop_command: server_stop
Servers.stop_command: server_stop,
Servers.backup_path: backup_path
}).execute()
try:
@ -343,5 +352,3 @@ class Controller:
self.servers_list.pop(counter)
counter += 1
controller = Controller()

View File

@ -0,0 +1,8 @@
class CraftyException(Exception):
pass
class DatabaseException(CraftyException):
pass
class SchemaError(DatabaseException):
pass

View File

@ -10,6 +10,8 @@ import socket
import random
import logging
import html
import zipfile
import pathlib
from datetime import datetime
from socket import gethostname
@ -36,6 +38,7 @@ class Helpers:
self.config_dir = os.path.join(self.root_dir, 'app', 'config')
self.webroot = os.path.join(self.root_dir, 'app', 'frontend')
self.servers_dir = os.path.join(self.root_dir, 'servers')
self.backup_path = os.path.join(self.root_dir, 'backups')
self.session_file = os.path.join(self.root_dir, 'app', 'config', 'session.lock')
self.settings_file = os.path.join(self.root_dir, 'app', 'config', 'config.json')
@ -43,6 +46,7 @@ class Helpers:
self.ensure_dir_exists(os.path.join(self.root_dir, 'app', 'config', 'db'))
self.db_path = os.path.join(self.root_dir, 'app', 'config', 'db', 'crafty.sqlite')
self.serverjar_cache = os.path.join(self.config_dir, 'serverjars.json')
self.credits_cache = os.path.join(self.config_dir, 'credits.json')
self.passhasher = PasswordHasher()
self.exiting = False
@ -305,6 +309,8 @@ class Helpers:
@staticmethod
def check_path_exists(path: str):
if not path:
return False
logger.debug('Looking for path: {}'.format(path))
if os.path.exists(path):
@ -371,6 +377,19 @@ class Helpers:
total += entry.stat(follow_symlinks=False).st_size
return total
@staticmethod
def list_dir_by_date(path: str, reverse=False):
return [str(p) for p in sorted(pathlib.Path(path).iterdir(), key=os.path.getmtime, reverse=reverse)]
def get_human_readable_files_sizes(self, paths: list):
sizes = []
for p in paths:
sizes.append({
"path": p,
"size": self.human_readable_file_size(os.stat(p).st_size)
})
return sizes
@staticmethod
def base64_encode_string(string: str):
s_bytes = str(string).encode('utf-8')
@ -520,7 +539,7 @@ class Helpers:
@staticmethod
def get_banned_players(server_id, db_helper):
stats = db_helper.get_server_stats_by_id(server_id)
server_path = stats[0]['server_id']['path']
server_path = stats['server_id']['path']
path = os.path.join(server_path, 'banned-players.json')
try:
@ -533,5 +552,13 @@ class Helpers:
return json.loads(content)
@staticmethod
def zip_directory(file, path, compression=zipfile.ZIP_LZMA):
with zipfile.ZipFile(file, 'w', compression) as zf:
for root, dirs, files in os.walk(path):
for file in files:
zf.write(os.path.join(root, file),
os.path.relpath(os.path.join(root, file),
os.path.join(path, '..')))
helper = Helpers()

View File

@ -9,6 +9,8 @@ from app.classes.minecraft.server_props import ServerProps
from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
try:
from peewee import *
@ -20,15 +22,30 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e, e.name))
sys.exit(1)
schema_version = (0, 1, 0) # major, minor, patch semver
database = SqliteDatabase(helper.db_path, pragmas={
'journal_mode': 'wal',
'cache_size': -1024 * 10})
class BaseModel(Model):
class Meta:
database = database
class SchemaVersion(BaseModel):
# DO NOT EVER CHANGE THE SCHEMA OF THIS TABLE
# (unless we have a REALLY good reason to)
# There will only ever be one row, and it allows the database loader to detect
# what it needs to do on major version upgrades so you don't have to wipe the DB
# every time you upgrade
schema_major = IntegerField()
schema_minor = IntegerField()
schema_patch = IntegerField()
class Meta:
table_name = 'schema_version'
primary_key = CompositeKey('schema_major', 'schema_minor', 'schema_patch')
class Users(BaseModel):
user_id = AutoField()
@ -97,6 +114,7 @@ class Servers(BaseModel):
server_uuid = CharField(default="", index=True)
server_name = CharField(default="Server", index=True)
path = CharField(default="")
backup_path = CharField(default="")
executable = CharField(default="")
log_path = CharField(default="")
execution_command = CharField(default="")
@ -111,16 +129,6 @@ class Servers(BaseModel):
class Meta:
table_name = "servers"
class User_Servers(BaseModel):
user_id = ForeignKeyField(Users, backref='user_server')
server_id = ForeignKeyField(Servers, backref='user_server')
class Meta:
table_name = 'user_servers'
primary_key = CompositeKey('user_id', 'server_id')
class Role_Servers(BaseModel):
role_id = ForeignKeyField(Roles, backref='role_server')
server_id = ForeignKeyField(Servers, backref='role_server')
@ -178,12 +186,25 @@ class Webhooks(BaseModel):
class Meta:
table_name = "webhooks"
class Schedules(BaseModel):
schedule_id = IntegerField(unique=True, primary_key=True)
server_id = ForeignKeyField(Servers, backref='schedule_server')
enabled = BooleanField()
action = CharField()
interval = IntegerField()
interval_type = CharField()
start_time = CharField(null=True)
command = CharField(null=True)
comment = CharField()
class Meta:
table_name = 'schedules'
class Backups(BaseModel):
directories = CharField()
storage_location = CharField()
directories = CharField(null=True)
max_backups = IntegerField()
server_id = IntegerField(index=True)
server_id = ForeignKeyField(Servers, backref='backups_server')
schedule_id = ForeignKeyField(Schedules, backref='backups_schedule')
class Meta:
table_name = 'backups'
@ -202,17 +223,23 @@ class db_builder:
Host_Stats,
Webhooks,
Servers,
User_Servers,
Role_Servers,
Server_Stats,
Commands,
Audit_Log
Audit_Log,
SchemaVersion,
Schedules
])
@staticmethod
def default_settings():
logger.info("Fresh Install Detected - Creating Default Settings")
console.info("Fresh Install Detected - Creating Default Settings")
SchemaVersion.insert({
SchemaVersion.schema_major: schema_version[0],
SchemaVersion.schema_minor: schema_version[1],
SchemaVersion.schema_patch: schema_version[2]
}).execute()
default_data = helper.find_default_password()
username = default_data.get("username", 'admin')
@ -239,9 +266,39 @@ class db_builder:
return True
pass
@staticmethod
def check_schema_version():
svs = SchemaVersion.select().execute()
if len(svs) != 1:
raise exceptions.SchemaError("Multiple or no schema versions detected - potentially a failed upgrade?")
sv = svs[0]
svt = (sv.schema_major, sv.schema_minor, sv.schema_patch)
logger.debug("Schema: found {}, expected {}".format(svt, schema_version))
console.debug("Schema: found {}, expected {}".format(svt, schema_version))
if sv.schema_major > schema_version[0]:
raise exceptions.SchemaError("Major version mismatch - possible code reversion")
elif sv.schema_major < schema_version[0]:
db_shortcuts.upgrade_schema()
if sv.schema_minor > schema_version[1]:
logger.warning("Schema minor mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
console.warning("Schema minor mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
elif sv.schema_minor < schema_version[1]:
db_shortcuts.upgrade_schema()
if sv.schema_patch > schema_version[2]:
logger.info("Schema patch mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
console.info("Schema patch mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
elif sv.schema_patch < schema_version[2]:
db_shortcuts.upgrade_schema()
logger.info("Schema validation successful! {}".format(schema_version))
class db_shortcuts:
@staticmethod
def upgrade_schema():
raise NotImplemented("I don't know who you are or how you reached this code, but this should NOT have happened. Please report it to the developer with due haste.")
@staticmethod
def return_rows(query):
rows = []
@ -258,13 +315,12 @@ class db_shortcuts:
@staticmethod
def get_server_data_by_id(server_id):
query = Servers.select().where(Servers.server_id == server_id).limit(1)
try:
query = Servers.get_by_id(server_id)
except DoesNotExist:
return db_helper.return_rows(query)[0]
except IndexError:
return {}
return model_to_dict(query)
@staticmethod
def get_all_defined_servers():
query = Servers.select()
@ -307,7 +363,7 @@ class db_shortcuts:
for s in servers:
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)})
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0]})
return server_data
@staticmethod
@ -352,7 +408,7 @@ class db_shortcuts:
@staticmethod
def get_server_stats_by_id(server_id):
stats = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).limit(1)
return db_helper.return_rows(stats)
return db_helper.return_rows(stats)[0]
@staticmethod
def server_id_exists(server_id):
@ -417,6 +473,8 @@ class db_shortcuts:
@staticmethod
def get_userid_by_name(username):
if username == "SYSTEM":
return 0
try:
return (Users.get(Users.username == username)).user_id
except DoesNotExist:
@ -424,6 +482,21 @@ class db_shortcuts:
@staticmethod
def get_user(user_id):
if user_id == 0:
return {
user_id: 0,
created: None,
last_login: None,
last_update: None,
last_ip: "127.27.23.89",
username: "SYSTEM",
password: None,
enabled: True,
superuser: False,
api_token: None,
roles: [],
servers: []
}
user = model_to_dict(Users.get(Users.user_id == user_id))
if user:
@ -432,13 +505,13 @@ class db_shortcuts:
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_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)
#for s in servers_query:
# servers.add(s.server_id.server_id)
user['roles'] = roles
user['servers'] = servers
#user['servers'] = servers
logger.debug("user: ({}) {}".format(user_id, user))
return user
else:
@ -478,10 +551,10 @@ class db_shortcuts:
# 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()
#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()
@ -658,8 +731,111 @@ class db_shortcuts:
Audit_Log.source_ip: source_ip
}).execute()
@staticmethod
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True):
sch_id = Schedules.insert({
Schedules.server_id: server_id,
Schedules.action: action,
Schedules.enabled: enabled,
Schedules.interval: interval,
Schedules.interval_type: interval_type,
Schedules.start_time: start_time,
Schedules.command: command,
Schedules.comment: comment
}).execute()
return sch_id
@staticmethod
def delete_scheduled_task(schedule_id):
sch = Schedules.get(Schedules.schedule_id == schedule_id)
return Schedules.delete_instance(sch)
@staticmethod
def update_scheduled_task(schedule_id, updates):
Schedules.update(updates).where(Schedules.schedule_id == schedule_id).execute()
@staticmethod
def get_scheduled_task(schedule_id):
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute()
@staticmethod
def get_schedules_by_server(server_id):
return Schedules.select().where(Schedules.server_id == server_id).execute()
@staticmethod
def get_schedules_all():
return Schedules.select().execute()
@staticmethod
def get_schedules_enabled():
return Schedules.select().where(Schedules.enabled == True).execute()
@staticmethod
def get_backup_config(server_id):
try:
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
conf = {
"backup_path": row.server_id.backup_path,
"directories": row.directories,
"max_backups": row.max_backups,
"auto_enabled": row.schedule_id.enabled,
"server_id": row.server_id.server_id
}
except IndexError:
conf = {
"backup_path": None,
"directories": None,
"max_backups": 0,
"auto_enabled": True,
"server_id": server_id
}
return conf
@staticmethod
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
logger.debug("Updating server {} backup config with {}".format(server_id, locals()))
try:
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
new_row = False
conf = {}
schd = {}
except IndexError:
conf = {
"directories": None,
"max_backups": 0,
"server_id": server_id
}
schd = {
"enabled": True,
"action": "backup_server",
"interval_type": "days",
"interval": 1,
"start_time": "00:00",
"server_id": server_id,
"comment": "Default backup job"
}
new_row = True
if max_backups is not None:
conf['max_backups'] = max_backups
schd['enabled'] = bool(auto_enabled)
if not new_row:
with database.atomic():
if backup_path is not None:
u1 = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id).execute()
else:
u1 = 0
u2 = Backups.update(conf).where(Backups.server_id == server_id).execute()
u3 = Schedules.update(schd).where(Schedules.schedule_id == row.schedule_id).execute()
logger.debug("Updating existing backup record. {}+{}+{} rows affected".format(u1, u2, u3))
else:
with database.atomic():
conf["server_id"] = server_id
if backup_path is not None:
u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
s = Schedules.create(**schd)
conf['schedule_id'] = s.schedule_id
b = Backups.create(**conf)
logger.debug("Creating new backup record.")
installer = db_builder()
db_helper = db_shortcuts()
db_helper = db_shortcuts()

View File

@ -9,6 +9,7 @@ import datetime
import threading
import schedule
import logging.config
import zipfile
from app.classes.shared.helpers import helper
@ -29,7 +30,7 @@ except ModuleNotFoundError as e:
class Server:
def __init__(self):
def __init__(self, stats):
# holders for our process
self.process = None
self.line = False
@ -45,6 +46,7 @@ class Server:
self.is_crashed = False
self.restart_count = 0
self.crash_watcher_schedule = None
self.stats = stats
def reload_server_settings(self):
server_data = db_helper.get_server_data_by_id(self.server_id)
@ -108,7 +110,6 @@ class Server:
helper.do_exit()
def start_server(self):
from app.classes.minecraft.stats import stats
# fail safe in case we try to start something already running
if self.check_running():
@ -155,7 +156,6 @@ class Server:
self.server_thread.join()
def stop_server(self):
from app.classes.minecraft.stats import stats
if self.settings['stop_command']:
self.send_command(self.settings['stop_command'])
@ -189,7 +189,7 @@ class Server:
# massive resetting of variables
self.cleanup_server_object()
stats.record_stats()
self.stats.record_stats()
def restart_threaded_server(self):
@ -318,3 +318,26 @@ class Server:
logger.info("Removing old crash detection watcher thread")
console.info("Removing old crash detection watcher thread")
schedule.clear(self.name)
def backup_server(self):
logger.info("Starting server {} (ID {}) backup".format(self.name, self.server_id))
conf = db_helper.get_backup_config(self.server_id)
try:
backup_filename = "{}/{}.zip".format(conf['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
logger.info("Creating backup of server '{}' (ID#{}) at '{}'".format(self.settings['server_name'], self.server_id, backup_filename))
helper.zip_directory(backup_filename, self.server_path)
backup_list = self.list_backups()
if len(self.list_backups()) > conf["max_backups"]:
oldfile = backup_list[0]
logger.info("Removing old backup '{}'".format(oldfile))
os.remove(oldfile)
except:
logger.exception("Failed to create backup of server {} (ID {})".format(self.name, self.server_id))
def list_backups(self):
conf = db_helper.get_backup_config(self.server_id)
if helper.check_path_exists(self.settings['backup_path']):
files = helper.get_human_readable_files_sizes(helper.list_dir_by_date(self.settings['backup_path']))
return [{"path": os.path.relpath(f['path'], start=conf['backup_path']), "size": f["size"]} for f in files]
else:
return []

View File

@ -8,11 +8,9 @@ import asyncio
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.web.tornado import webserver
from app.classes.web.tornado import Webserver
from app.classes.web.websocket_helper import websocket_helper
from app.classes.minecraft.stats import stats
from app.classes.shared.controller import controller
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.shared.models import db_helper
@ -26,11 +24,26 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e, e.name))
sys.exit(1)
scheduler_intervals = { 'seconds',
'minutes',
'hours',
'days',
'weeks',
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday'
}
class TasksManager:
def __init__(self):
self.tornado = webserver()
def __init__(self, controller):
self.controller = controller
self.tornado = Webserver(controller, self)
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread')
self.main_kill_switch_thread = threading.Thread(target=self.main_kill_switch, daemon=True, name="main_loop")
@ -39,13 +52,13 @@ class TasksManager:
self.schedule_thread = threading.Thread(target=self.scheduler_thread, daemon=True, name="scheduler")
self.log_watcher_thread = threading.Thread(target=self.log_watcher, daemon=True, name="log_watcher")
self.log_watcher_thread.start()
self.command_thread = threading.Thread(target=self.command_watcher, daemon=True, name="command_watcher")
self.command_thread.start()
self.realtime_thread = threading.Thread(target=self.realtime, daemon=True, name="realtime")
self.realtime_thread.start()
self.reload_schedule_from_db()
def get_main_thread_run_status(self):
return self.main_thread_exiting
@ -60,14 +73,29 @@ class TasksManager:
self._main_graceful_exit()
time.sleep(5)
@staticmethod
def command_watcher():
def reload_schedule_from_db(self):
jobs = db_helper.get_schedules_enabled()
schedule.clear(tag='backup')
schedule.clear(tag='db')
for j in jobs:
if j.interval_type in scheduler_intervals:
logger.info("Loading schedule ID#{i}: '{a}' every {n} {t} at {s}".format(
i=j.schedule_id, a=j.action, n=j.interval, t=j.interval_type, s=j.start_time))
try:
getattr(schedule.every(j.interval), j.interval_type).at(j.start_time).do(
db_helper.send_command, 0, j.server_id, "127.27.23.89", j.action)
except schedule.ScheduleValueError as e:
logger.critical("Scheduler value error occurred: {} on ID#{}".format(e, j.schedule_id))
else:
logger.critical("Unknown schedule job type '{}' at id {}, skipping".format(j.interval_type, j.schedule_id))
def command_watcher(self):
while True:
# select any commands waiting to be processed
commands = db_helper.get_unactioned_commands()
for c in commands:
svr = controller.get_server_obj(c['server_id']['server_id'])
svr = self.controller.get_server_obj(c['server_id']['server_id'])
command = c.get('command', None)
if command == 'start_server':
@ -79,6 +107,9 @@ class TasksManager:
elif command == "restart_server":
svr.restart_threaded_server()
elif command == "backup_server":
svr.backup_server()
db_helper.mark_command_complete(c.get('command_id', None))
time.sleep(1)
@ -88,7 +119,7 @@ class TasksManager:
os.remove(helper.session_file)
os.remove(os.path.join(helper.root_dir, 'exit.txt'))
os.remove(os.path.join(helper.root_dir, '.header'))
controller.stop_all_servers()
self.controller.stop_all_servers()
except:
logger.info("Caught error during shutdown", exc_info=True)
@ -113,6 +144,15 @@ class TasksManager:
logger.info("Launching Scheduler Thread...")
console.info("Launching Scheduler Thread...")
self.schedule_thread.start()
logger.info("Launching command thread...")
console.info("Launching command thread...")
self.command_thread.start()
logger.info("Launching log watcher...")
console.info("Launching log watcher...")
self.log_watcher_thread.start()
logger.info("Launching realtime thread...")
console.info("Launching realtime thread...")
self.realtime_thread.start()
@staticmethod
def scheduler_thread():
@ -120,17 +160,16 @@ class TasksManager:
schedule.run_pending()
time.sleep(1)
@staticmethod
def start_stats_recording():
def start_stats_recording(self):
stats_update_frequency = helper.get_setting('stats_update_frequency')
logger.info("Stats collection frequency set to {stats} seconds".format(stats=stats_update_frequency))
console.info("Stats collection frequency set to {stats} seconds".format(stats=stats_update_frequency))
# one for now,
stats.record_stats()
self.controller.stats.record_stats()
# one for later
schedule.every(stats_update_frequency).seconds.do(stats.record_stats)
schedule.every(stats_update_frequency).seconds.do(self.controller.stats.record_stats).tag('stats-recording')
@staticmethod
def serverjar_cache_refresher():
@ -138,7 +177,7 @@ class TasksManager:
server_jar_obj.refresh_cache()
logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
schedule.every(12).hours.do(server_jar_obj.refresh_cache)
schedule.every(12).hours.do(server_jar_obj.refresh_cache).tag('serverjars')
@staticmethod
def realtime():
@ -174,4 +213,5 @@ class TasksManager:
def log_watcher(self):
console.debug('in log_watcher')
helper.check_for_old_logs(db_helper)
schedule.every(6).hours.do(lambda: helper.check_for_old_logs(db_helper))
schedule.every(6).hours.do(lambda: helper.check_for_old_logs(db_helper)).tag('log-mgmt')

View File

@ -9,7 +9,6 @@ import shutil
from app.classes.shared.console import console
from app.classes.shared.models import Users, installer
from app.classes.web.base_handler import BaseHandler
from app.classes.shared.controller import controller
from app.classes.shared.models import db_helper
from app.classes.shared.helpers import helper
@ -56,8 +55,8 @@ class AjaxHandler(BaseHandler):
logger.warning("Server Data not found in server_log ajax call")
self.redirect("/panel/error?error=Server ID Not Found")
if server_data['log_path']:
logger.warning("Server ID not found in server_log ajax call ({})".format(server_id))
if not server_data['log_path']:
logger.warning("Log path not found in server_log ajax call ({})".format(server_id))
if full_log:
log_lines = helper.get_setting('max_log_lines')
@ -149,7 +148,7 @@ class AjaxHandler(BaseHandler):
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 = self.controller.get_server_obj(server_id)
if command:
if srv_obj.check_running():
@ -219,7 +218,6 @@ class AjaxHandler(BaseHandler):
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")
@ -234,7 +232,9 @@ class AjaxHandler(BaseHandler):
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) \
server_info = db_helper.get_server_data_by_id(server_id)
if not helper.in_path(server_info['path'], file_path) \
or not helper.in_path(server_info['backup_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))
@ -261,7 +261,9 @@ class AjaxHandler(BaseHandler):
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) \
server_info = db_helper.get_server_data_by_id(server_id)
if not helper.in_path(server_info['path'], dir_path) \
or not helper.in_path(server_info['backup_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))

View File

@ -5,13 +5,13 @@ import tornado.web
import tornado.escape
import logging
from app.classes.web.base_handler import BaseHandler
from app.classes.shared.models import Users
from app.classes.minecraft.stats import stats
log = logging.getLogger(__name__)
class BaseHandler(tornado.web.RequestHandler):
class ApiHandler(BaseHandler):
def return_response(self, data: dict):
# Define a standardized response
@ -25,6 +25,7 @@ class BaseHandler(tornado.web.RequestHandler):
def authenticate_user(self):
try:
log.debug("Searching for specified token")
# TODO: YEET THIS
user_data = Users.get(api_token=self.get_argument('token'))
log.debug("Checking results")
if user_data:
@ -40,19 +41,19 @@ class BaseHandler(tornado.web.RequestHandler):
pass
class ServersStats(BaseHandler):
class ServersStats(ApiHandler):
def get(self):
"""Get details about all servers"""
self.authenticate_user()
# Get server stats
self.finish(self.write({"servers": stats.get_servers_stats()}))
self.finish(self.write({"servers": self.controller.stats.get_servers_stats()}))
class NodeStats(BaseHandler):
class NodeStats(ApiHandler):
def get(self):
"""Get stats for particular node"""
self.authenticate_user()
# Get node stats
node_stats = stats.get_node_stats()
node_stats = self.controller.stats.get_node_stats()
node_stats.pop("servers")
self.finish(self.write(node_stats))

View File

@ -1,11 +1,21 @@
import logging
import tornado.web
import bleach
from typing import (
Union,
List,
Optional
)
logger = logging.getLogger(__name__)
class BaseHandler(tornado.web.RequestHandler):
def initialize(self, controller=None, tasks_manager=None):
self.controller = controller
self.tasks_manager = tasks_manager
def get_remote_ip(self):
remote_ip = self.request.headers.get("X-Real-IP") or \
self.request.headers.get("X-Forwarded-For") or \
@ -14,3 +24,28 @@ class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_secure_cookie("user", max_age_days=1)
def autobleach(self, text):
if type(text) is bool:
return text
else:
return text
def get_argument(
self,
name: str,
default: Union[None, str, tornado.web._ArgDefaultMarker] = tornado.web._ARG_DEFAULT,
strip: bool = True,
) -> Optional[str]:
arg = self._get_argument(name, default, self.request.arguments, strip)
logger.debug("Bleaching {}: {}".format(name, arg))
return bleach.clean(arg)
def get_arguments(self, name: str, strip: bool = True) -> List[str]:
assert isinstance(strip, bool)
args = self._get_arguments(name, self.request.arguments, strip)
args_ret = []
for arg in args:
logger.debug("Bleaching {}: {}".format(name, arg))
args_ret += bleach.clean(arg)
return args_ret

View File

@ -5,14 +5,13 @@ import tornado.escape
import bleach
import time
import datetime
import os
from app.classes.shared.console import console
from app.classes.shared.models import Users, installer
from app.classes.web.base_handler import BaseHandler
from app.classes.shared.controller import controller
from app.classes.shared.models import db_helper, Servers
from app.classes.shared.helpers import helper
from app.classes.minecraft.stats import stats
logger = logging.getLogger(__name__)
@ -50,8 +49,8 @@ class PanelHandler(BaseHandler):
'user_role' : user_role,
'server_stats': {
'total': len(defined_servers),
'running': len(controller.list_running_servers()),
'stopped': (len(controller.list_defined_servers()) - len(controller.list_running_servers()))
'running': len(self.controller.list_running_servers()),
'stopped': (len(self.controller.list_defined_servers()) - len(self.controller.list_running_servers()))
},
'menu_servers': defined_servers,
'hosts_data': db_helper.get_latest_hosts_stats(),
@ -64,7 +63,7 @@ class PanelHandler(BaseHandler):
if page_data['server_stats']['total'] == 0 and page != "error":
self.set_status(301)
self.redirect("/server/step1")
return False
return
if page == 'unauthorized':
template = "panel/denied.html"
@ -73,6 +72,11 @@ class PanelHandler(BaseHandler):
template = "public/error.html"
elif page == 'credits':
with open(helper.credits_cache) as republic_credits_will_do:
credits = json.load(republic_credits_will_do)
page_data["patreons"] = credits["patreons"]
page_data["staff"] = credits["staff"]
page_data["translations"] = credits["translations"]
template = "panel/credits.html"
elif page == 'contribute':
@ -88,7 +92,7 @@ class PanelHandler(BaseHandler):
server_id,
self.get_remote_ip())
controller.remove_server(server_id)
self.controller.remove_server(server_id)
self.redirect("/panel/dashboard")
return
@ -114,14 +118,14 @@ class PanelHandler(BaseHandler):
if server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return False
return
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return False
return
if user['superuser'] != 1:
#if not db_helper.server_id_authorized(server_id, userId):
@ -129,17 +133,21 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID")
return False
valid_subpages = ['term', 'logs', 'config', 'files', 'admin_controls']
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls']
if subpage not in valid_subpages:
logger.debug('not a valid subpage')
subpage = 'term'
logger.debug('Subpage: "{}"'.format(subpage))
server = self.controller.get_server_obj(server_id)
# 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)
page_data['server_stats'] = db_helper.get_server_stats_by_id(server_id)
page_data['get_players'] = lambda: stats.get_server_players(server_id)
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
if subpage == "backup":
page_data['backup_config'] = db_helper.get_backup_config(server_id)
page_data['backup_list'] = server.list_backups()
def get_banned_players_html():
banned_players = helper.get_banned_players(server_id, db_helper)
@ -166,6 +174,79 @@ class PanelHandler(BaseHandler):
# template = "panel/server_details.html"
template = "panel/server_{subpage}.html".format(subpage=subpage)
elif page == 'download_backup':
server_id = self.get_argument('id', None)
file = self.get_argument('file', "")
if server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return
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
server_info = db_helper.get_server_data_by_id(server_id)
backup_file = os.path.abspath(os.path.join(server_info["backup_path"], file))
if not helper.in_path(server_info["backup_path"], backup_file) \
or not os.path.isfile(backup_file):
self.redirect("/panel/error?error=Invalid path detected")
return
self.set_header('Content-Type', 'application/octet-stream')
self.set_header('Content-Disposition', 'attachment; filename=' + file)
chunk_size = 1024 * 1024 * 4 # 4 MiB
with open(backup_file, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
try:
self.write(chunk) # write the chunk to response
self.flush() # send the chunk to client
except iostream.StreamClosedError:
# this means the client has closed the connection
# so break the loop
break
finally:
# deleting the chunk is very important because
# if many clients are downloading files at the
# same time, the chunks in memory will keep
# increasing and will eat up the RAM
del chunk
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
elif page == 'backup_now':
server_id = self.get_argument('id', None)
if server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return
else:
# does this server id exist?
if not db_helper.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return
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
server = self.controller.get_server_obj(server_id).backup_server()
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
elif page == 'panel_config':
page_data['users'] = db_helper.get_all_users()
page_data['roles'] = db_helper.get_all_roles()
@ -194,10 +275,10 @@ class PanelHandler(BaseHandler):
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
return
page_data['roles_all'] = db_helper.get_all_roles()
page_data['servers_all'] = controller.list_defined_servers()
page_data['servers_all'] = self.controller.list_defined_servers()
template = "panel/panel_edit_user.html"
elif page == "edit_user":
@ -205,16 +286,16 @@ class PanelHandler(BaseHandler):
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()
page_data['servers_all'] = self.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
return
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
return
if exec_user['user_id'] != page_data['user']['user_id']:
page_data['user']['api_token'] = "********"
@ -228,19 +309,19 @@ class PanelHandler(BaseHandler):
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
return
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
return
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
return
elif target_user['superuser']:
self.redirect("/panel/error?error=Cannot remove a superuser")
return False
return
db_helper.remove_user(user_id)
@ -263,25 +344,25 @@ class PanelHandler(BaseHandler):
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
return
page_data['servers_all'] = controller.list_defined_servers()
page_data['servers_all'] = self.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()
page_data['servers_all'] = self.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
return
elif role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return False
return
template = "panel/panel_edit_role.html"
@ -293,16 +374,16 @@ class PanelHandler(BaseHandler):
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
return
elif role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return False
return
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
return
db_helper.remove_role(role_id)
@ -348,17 +429,15 @@ class PanelHandler(BaseHandler):
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
return
elif server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return False
return
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return False
return
Servers.update({
Servers.server_name: server_name,
@ -375,7 +454,7 @@ class PanelHandler(BaseHandler):
Servers.logs_delete_after: logs_delete_after,
}).where(Servers.server_id == server_id).execute()
controller.refresh_server_settings(server_id)
self.controller.refresh_server_settings(server_id)
db_helper.add_to_audit_log(user_data['user_id'],
"Edited server {} named {}".format(server_id, server_name),
@ -384,6 +463,41 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/server_detail?id={}&subpage=config".format(server_id))
if page == "server_backup":
logger.debug(self.request.arguments)
server_id = self.get_argument('id', None)
backup_path = bleach.clean(self.get_argument('backup_path', None))
max_backups = bleach.clean(self.get_argument('max_backups', None))
enabled = int(float(bleach.clean(self.get_argument('auto_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
elif server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return
else:
# does this server id exist?
if not db_helper.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return
if backup_path is not None:
Servers.update({
Servers.backup_path: backup_path
}).where(Servers.server_id == server_id).execute()
db_helper.set_backup_config(server_id, max_backups=max_backups)
db_helper.add_to_audit_log(user_data['user_id'],
"Edited server {}: updated backups".format(server_id),
server_id,
self.get_remote_ip())
self.tasks_manager.reload_schedule_from_db()
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
elif page == "edit_user":
user_id = bleach.clean(self.get_argument('id', None))
username = bleach.clean(self.get_argument('username', None))
@ -397,22 +511,22 @@ class PanelHandler(BaseHandler):
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
return
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return False
return
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
return
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
return
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
return False
return
roles = set()
for role in db_helper.get_all_roles():
@ -425,7 +539,7 @@ class PanelHandler(BaseHandler):
roles.add(role.role_id)
servers = set()
for server in controller.list_defined_servers():
for server in self.controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
@ -461,19 +575,19 @@ class PanelHandler(BaseHandler):
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
return
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return False
return
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
return
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
return False
return
roles = set()
for role in db_helper.get_all_roles():
@ -486,7 +600,7 @@ class PanelHandler(BaseHandler):
roles.add(role['role_id'])
servers = set()
for server in controller.list_defined_servers():
for server in self.controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
@ -517,21 +631,21 @@ class PanelHandler(BaseHandler):
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
return
elif role_name is None or role_name == "":
self.redirect("/panel/error?error=Invalid username")
return False
return
elif role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return False
return
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
return
servers = set()
for server in controller.list_defined_servers():
for server in self.controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
@ -560,18 +674,18 @@ class PanelHandler(BaseHandler):
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
return
elif role_name is None or role_name == "":
self.redirect("/panel/error?error=Invalid role name")
return False
return
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
return
servers = set()
for server in controller.list_defined_servers():
for server in self.controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
@ -592,3 +706,7 @@ class PanelHandler(BaseHandler):
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
else:
self.set_status(404)
self.render("public/404.html")

View File

@ -6,10 +6,8 @@ import shutil
from app.classes.shared.console import console
from app.classes.web.base_handler import BaseHandler
from app.classes.shared.controller import controller
from app.classes.shared.models import db_helper, Servers
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import stats
from app.classes.shared.helpers import helper
@ -48,16 +46,16 @@ class ServerHandler(BaseHandler):
template = "public/404.html"
defined_servers = controller.list_defined_servers()
defined_servers = self.controller.list_defined_servers()
page_data = {
'version_data': helper.get_version_string(),
'user_data': user_data,
'user_role' : user_role,
'server_stats': {
'total': len(controller.list_defined_servers()),
'running': len(controller.list_running_servers()),
'stopped': (len(controller.list_defined_servers()) - len(controller.list_running_servers()))
'total': len(self.controller.list_defined_servers()),
'running': len(self.controller.list_running_servers()),
'stopped': (len(self.controller.list_defined_servers()) - len(self.controller.list_running_servers()))
},
'hosts_data': db_helper.get_latest_hosts_stats(),
'menu_servers': defined_servers,
@ -144,7 +142,7 @@ class ServerHandler(BaseHandler):
Servers.stop_command: stop_command
}).execute()
controller.init_all_servers()
self.controller.init_all_servers()
console.debug('initted all servers')
return
@ -164,26 +162,26 @@ class ServerHandler(BaseHandler):
server_parts = server.split("|")
if import_type == 'import_jar':
good_path = controller.verify_jar_server(import_server_path, import_server_jar)
good_path = self.controller.verify_jar_server(import_server_path, import_server_jar)
if not good_path:
self.redirect("/panel/error?error=Server path or Server Jar not found!")
return False
new_server_id = controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
elif import_type == 'import_zip':
good_path = controller.verify_zip_server(import_server_path)
good_path = self.controller.verify_zip_server(import_server_path)
if not good_path:
self.redirect("/panel/error?error=Zip file not found!")
return False
new_server_id = controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
new_server_id = self.controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
if new_server_id == "false":
self.redirect("/panel/error?error=ZIP file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
return False
else:
# todo: add server type check here and call the correct server add functions if not a jar
new_server_id = controller.create_jar_server(server_parts[0], server_parts[1], server_name, min_mem, max_mem, port)
new_server_id = self.controller.create_jar_server(server_parts[0], server_parts[1], server_name, min_mem, max_mem, port)
if new_server_id:
db_helper.add_to_audit_log(user_data['user_id'],
@ -194,7 +192,7 @@ class ServerHandler(BaseHandler):
logger.error("Unable to create server")
console.error("Unable to create server")
stats.record_stats()
self.controller.stats.record_stats()
self.redirect("/panel/dashboard")
self.render(

View File

@ -33,12 +33,14 @@ except ModuleNotFoundError as e:
class webserver:
class Webserver:
def __init__(self):
def __init__(self, controller, tasks_manager):
self.ioloop = None
self.HTTP_Server = None
self.HTTPS_Server = None
self.controller = controller
self.tasks_manager = tasks_manager
self._asyncio_patch()
@ -118,15 +120,16 @@ class webserver:
tornado.locale.set_default_locale(lang)
handler_args = {"controller": self.controller, "tasks_manager": self.tasks_manager}
handlers = [
(r'/', DefaultHandler),
(r'/public/(.*)', PublicHandler),
(r'/panel/(.*)', PanelHandler),
(r'/server/(.*)', ServerHandler),
(r'/ajax/(.*)', AjaxHandler),
(r'/api/stats/servers', ServersStats),
(r'/api/stats/node', NodeStats),
(r'/ws', SocketHandler),
(r'/', DefaultHandler, handler_args),
(r'/public/(.*)', PublicHandler, handler_args),
(r'/panel/(.*)', PanelHandler, handler_args),
(r'/server/(.*)', ServerHandler, handler_args),
(r'/ajax/(.*)', AjaxHandler, handler_args),
(r'/api/stats/servers', ServersStats, handler_args),
(r'/api/stats/node', NodeStats, handler_args),
(r'/ws', SocketHandler, handler_args),
]
app = tornado.web.Application(

View File

@ -8,6 +8,10 @@ from app.classes.web.websocket_helper import websocket_helper
class SocketHandler(tornado.websocket.WebSocketHandler):
def initialize(self, controller=None, tasks_manager=None):
self.controller = controller
self.tasks_manager = tasks_manager
def get_remote_ip(self):
remote_ip = self.request.headers.get("X-Real-IP") or \
self.request.headers.get("X-Forwarded-For") or \

137
app/config/credits.json Normal file
View File

@ -0,0 +1,137 @@
{
"patreons": [
{
"name": "Richard B",
"level": "substainer"
},
{
"name": "John C",
"level": "advocate"
},
{
"name": "Nicolas T",
"level": "substainer"
},
{
"name": "Lightkeeper",
"level": "substainer"
},
{
"name": "test user 1",
"level": "supporter"
},
{
"name": "test user 2",
"level": "other"
}
],
"staff": {
"development": [
{
"name": "Phil Tarrant",
"title": "Benevolent Dictator for Life",
"loc": "Atlanta, GA",
"tags": [ "Staff", "Developer", [ "BDFL", "https://en.wikipedia.org/wiki/Benevolent_dictator_for_life" ] ],
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
"pic": "/static/assets/images/credits/ptarrant_cropped.png"
},
{
"name": "Pita Bread",
"title": null,
"loc": "Midwest, USA",
"tags": [ "Staff", null, "Community Leader" ],
"blurb": "His interests include bread, Linux, and networking. He enjoys pumpkins, organizing, and long-winded emails, but hates wifi.",
"pic": "/static/assets/images/credits/pita_cropped.png"
},
{
"name": "macgeek",
"title": null,
"loc": "Midwest, USA",
"tags": [ "Staff", "Developer", "Project Manager" ],
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
"pic": "/static/assets/images/credits/macgeek_cropped.png"
},
{
"name": "parzivaldewey",
"title": null,
"loc": "East Coast, USA",
"tags": [ "Staff", "Developer", "Support Manager" ],
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
"pic": "/static/assets/images/credits/andrew_cropped.png"
},
{
"name": "MC Gaming",
"title": null,
"loc": "Central, UK",
"tags": [ "Staff", "Developer", null ],
"blurb": "His interests include learning, Linux, programming. He loves pentesting apps and gaming.",
"pic": "/static/assets/images/credits/mcgaming.png"
},
{
"name": "Silversthorn",
"title": null,
"loc": null,
"tags": [ "Staff", "Developer", null ],
"blurb": "Often in it's cave, he sometimes goes out to help or do silly jokes. He's an IT clown (not the film), but seriously do the job when it's needed.",
"pic": "/static/assets/images/credits/silversthorn.png"
},
{
"name": "ThatOneLukas",
"title": null,
"loc": "Helsinki, FI",
"tags": [ "Staff", "Developer", null ],
"blurb": "Lukas enjoys bashing his head at the table while his code does not work",
"pic": "/static/assets/images/credits/lukas_cropped.png"
}
],
"support": [
{
"name": "iSilverfyre",
"title": null,
"loc": null,
"tags": [ "Staff", "Wiki", null ],
"blurb": "Silver enjoys helping others with their computer needs, writing documentation and loving her cat.",
"pic": "/static/assets/images/credits/isilverfyre.png"
},
{
"name": "Quentin",
"title": null,
"loc": null,
"tags": [ "Staff", "Developer", null ],
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
"pic": "/static/assets/images/credits/qub3d.png"
}
],
"retired": [
{
"name": "Kev Dagoat",
"title": "Head of Development",
"loc": "East Coast, AU",
"tags": [ "Staff", "Developer", "HOD" ],
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and Geeking over video cards.",
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
},
{
"name": "Manu",
"title": null,
"loc": "Eastern, CA",
"tags": [ "Staff", "Developer", "Project Manager" ],
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
"pic": "/static/assets/images/credits/manu_cropped.png"
},
{
"name": "UltraBlack",
"title": null,
"loc": "Bavaria, DE",
"tags": [ "Staff", null, "Idea Manager" ],
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
"pic": "/static/assets/images/credits/ultrablack_cropped.png"
}
]
},
"translations": {
"UltraBlack": [ "German" ],
"Manu": [ "French" ],
"ptarrant": [ "Sarcasm", "Wit" ]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -27,9 +27,76 @@
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="far fa-code"></i> &nbsp;Development Team</h4>
</div>
<div class="row">
{% for person in data['staff']['development'] %}
<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="{{ person['pic'] }}"
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">{{ person['name'] }}</h4>
</div>
<div class="wrapper d-flex align-items-center font-weight-medium text-muted">
{% if person['loc'] %}
<i class="mdi mdi-map-marker-outline mr-2"></i>
<p class="mb-0 text-muted">{{ person['loc'] }}</p>
{% end %}
</div>
</div>
</div>
<div class="col-md-8">
<div class="wrapper d-flex align-items-start">
{% if person['tags'][0] %}
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
{% end %}
{% if person['tags'][1] %}
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
{% end %}
{% if person['tags'][2] %}
{% if type(person['tags'][2]) is list %}
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2][0] }}</a>
{% else %}
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
{% end %}
{% end %}
</div>
<div class="wrapper d-flex align-items-start pt-3">
{% if person['title'] %}
Crafty's {{ person['title'] }}<br /><br />
{% end %}
{{ person['blurb'] }}
</div>
</div>
</div>
</div>
</div>
</div>
{% end %}
</div> <!-- end of user row -->
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fa fa-book"></i> &nbsp;Support and Documentation Team</h4>
</div>
<div class="row">
{% for person in data['staff']['support'] %}
<div class="col-md-6 mb-5">
<div class="card rounded shadow-none">
<div class="card-body">
@ -38,74 +105,46 @@
<div class="col-md-4">
<div class="user-avatar mb-auto">
<img src="/static/assets/images/credits/ptarrant_cropped.png"
<img src="{{ person['pic'] }}"
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">Phillip Tarrant</h4>
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
</div>
<div class="wrapper d-flex align-items-center font-weight-medium text-muted">
{% if person['loc'] %}
<i class="mdi mdi-map-marker-outline mr-2"></i>
<p class="mb-0 text-muted">Atlanta, GA</p>
<p class="mb-0 text-muted">{{ person['loc'] }}</p>
{% end %}
</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>
<a href="https://en.wikipedia.org/wiki/Benevolent_dictator_for_life" class="btn btn-sm btn-inverse-success mr-2">BDFL</a>
{% if person['tags'][0] %}
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
{% end %}
{% if person['tags'][1] %}
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
{% end %}
{% if person['tags'][2] %}
{% if type(person['tags'][2]) is list %}
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2][0] }}</a>
{% else %}
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
{% end %}
{% end %}
</div>
<div class="wrapper d-flex align-items-start pt-3">
Crafty's Benevolent Dictator for Life.<br /><br />
His interests include Linux, cybersecurity, hacking, and gaming.
He enjoys downtime with the family, and playing with his chickens.
</div>
</div>
</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/pita_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">Pita Bread</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">Midwest, USA</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-inverse-success mr-2">Community Leader</span>
</div>
<div class="wrapper d-flex align-items-start pt-3">
His interests include bread, Linux, and networking.
He enjoys pumpkins, organizing, and long-winded emails, but hates wifi.
{% if person['title'] %}
Crafty's {{ person['title'] }}<br /><br />
{% end %}
{{ person['blurb'] }}
</div>
</div>
</div>
@ -113,99 +152,17 @@
</div>
</div>
</div>
{% end %}
</div> <!-- end user row-->
<div class="row">
<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/kevdagoat.jpeg"
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">Kev Dagoat</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">East Coast, AU</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>
<span class="btn btn-sm btn-inverse-success mr-2">HOD</span>
</div>
<div class="wrapper d-flex align-items-start pt-3">
Crafty's Head Of Development<br /><br />
His interests include Linux, programming, and goats of course.
He enjoys building APIs, K8s, and Geeking over video cards.
</div>
</div>
</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/mcgaming.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">MC Gaming</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">Central, UK</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 learning, Linux, programming.
He loves pentesting apps and gaming.
</div>
</div>
</div>
</div>
</div>
</div>
</div> <!-- end user row-->
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="far fa-server"></i> &nbsp;Retired Staff</h4>
</div>
<div class="row">
{% for person in data['staff']['retired'] %}
<div class="col-md-6 mb-5">
<div class="card rounded shadow-none">
<div class="card-body">
@ -214,200 +171,46 @@
<div class="col-md-4">
<div class="user-avatar mb-auto">
<img src="/static/assets/images/credits/andrew_cropped.png"
<img src="{{ person['pic'] }}"
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">Andrew Redacted</h4>
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
</div>
<div class="wrapper d-flex align-items-center font-weight-medium text-muted">
{% if person['loc'] %}
<i class="mdi mdi-map-marker-outline mr-2"></i>
<p class="mb-0 text-muted">East Coast, USA</p>
<p class="mb-0 text-muted">{{ person['loc'] }}</p>
{% end %}
</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-inverse-success mr-2">Support Manager</span>
{% if person['tags'][0] %}
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
{% end %}
{% if person['tags'][1] %}
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
{% end %}
{% if person['tags'][2] %}
{% if type(person['tags'][2]) is list %}
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2][0] }}</a>
{% else %}
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
{% end %}
{% end %}
</div>
<div class="wrapper d-flex align-items-start pt-3">
His interests include Linux, gaming, and helping others. When he's able to
unplug he enjoys biking, hiking, and playing soccer.
</div>
</div>
</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/manu_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">Manu Redacted</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">Eastern, CA</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>
<span class="btn btn-sm btn-inverse-success mr-2">Project Manager</span>
</div>
<div class="wrapper d-flex align-items-start pt-3">
His interests include learning, Linux, and programming.
He enjoys speaking French, doing 6 things at once, and testing software.
</div>
</div>
</div>
</div>
</div>
</div>
</div> <!-- end user row-->
<div class="row">
<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/ultrablack_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">UltraBlack</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">Bavaria, DE</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-inverse-success mr-2">Idea Manager</span>
</div>
<div class="wrapper d-flex align-items-start pt-3">
Hi, my name is Tim, and I'm a huge fan of linux.
I'm often gaming and occasionally coding.
</div>
</div>
</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/macgeek_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">Mac Geek</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">Eastern, USA</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>
<span class="btn btn-sm btn-inverse-success mr-2">Support Manager</span>
</div>
<div class="wrapper d-flex align-items-start pt-3">
His interests include all things programming, and Pokemon.
He enjoys a good tech tangent, gaming, and playing on his phone.
</div>
</div>
</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.
{% if person['title'] %}
Crafty's {{ person['title'] }}<br /><br />
{% end %}
{{ person['blurb'] }}
</div>
</div>
</div>
@ -415,9 +218,9 @@
</div>
</div>
</div>
{% end %}
</div> <!-- end user row-->
</div>
</div>
</div>
@ -438,31 +241,22 @@
</tr>
</thead>
<tbody>
{% for pat in data["patreons"] %}
<tr>
<td>Richard B</td>
<td>{{ pat["name"] }}</td>
<td>
{% if pat["level"] == "substainer" %}
<span class="btn btn-sm btn-info mr-2">Substainer</span>
</td>
</tr>
<tr>
<td>John C</td>
<td>
{% elif pat["level"] == "advocate" %}
<span class="btn btn-sm btn-primary mr-2">Advocate</span>
{% elif pat["level"] == "supporter" %}
<span class="btn btn-sm btn-inverse-success mr-2">Supporter</span>
{% else %}
<span class="btn btn-sm btn-secondary mr-2">Other</span>
{% end %}
</td>
</tr>
<tr>
<td>Nicolas T</td>
<td>
<span class="btn btn-sm btn-info mr-2">Substainer</span>
</td>
</tr>
<tr>
<td>Lightkeeper</td>
<td>
<span class="btn btn-sm btn-info mr-2">Substainer</span>
</td>
</tr>
{% end %}
</tbody>
</table>
@ -483,27 +277,16 @@
</tr>
</thead>
<tbody>
{% for person in data['translations'] %}
<tr>
<td>Ultrablack</td>
<td>{{ person }}</td>
<td>
<span class="btn btn-sm btn-inverse-success mr-2">German</span>
{% for language in data['translations'][person] %}
<span class="btn btn-sm btn-inverse-success mr-2">{{ language }}</span>
{% end %}
</td>
</tr>
<tr>
<td>Manu</td>
<td>
<span class="btn btn-sm btn-inverse-success mr-2">French</span>
</td>
</tr>
<tr>
<td>ptarrant</td>
<td>
<span class="btn btn-sm btn-inverse-success mr-2">Sarcasm</span>
</td>
</tr>
{% end %}
</tbody>
</table>
</div>

View File

@ -125,7 +125,7 @@
<td class="actions_serverlist">
{% if server['stats'][0]['running'] %}
{% if server['stats']['running'] %}
<a class="stop_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-stop"></i></a> &nbsp;
<a class="restart_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-sync"></i></a> &nbsp;
{% else %}
@ -136,59 +136,59 @@
</td>
<td>
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats'][0]['cpu']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats'][0]['cpu'] <= 33 %}
{% if server['stats']['cpu'] <= 33 %}
bg-success
{% elif 34 <= server['stats'][0]['cpu'] <= 66 %}
{% elif 34 <= server['stats']['cpu'] <= 66 %}
bg-warning
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats'][0]['cpu']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats'][0]['cpu']}}%
{{server['stats']['cpu']}}%
</td>
<td>
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats'][0]['mem']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats'][0]['mem_percent'] <= 33 %}
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
{% elif 34 <= server['stats'][0]['mem_percent'] <= 66 %}
{% elif 34 <= server['stats']['mem_percent'] <= 66 %}
bg-warning
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats'][0]['mem_percent']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats'][0]['mem_percent']}}% -
{{server['stats']['mem_percent']}}% -
{% if server['stats'][0]['mem'] == 0 %}
{% if server['stats']['mem'] == 0 %}
0 MB
{% else %}
{{server['stats'][0]['mem']}}
{{server['stats']['mem']}}
{% end %}
</td>
<td>
{{ server['stats'][0]['world_name'] }} : {{ server['stats'][0]['world_size'] }}
{{ server['stats']['world_name'] }} : {{ server['stats']['world_size'] }}
</td>
<td>
{% if server['stats'][0]['int_ping_results'] %}
{{ server['stats'][0]['online'] }} / {{ server['stats'][0]['max'] }} Max<br />
{% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} Max<br />
{% if server['stats'][0]['desc'] != 'False' %}
{{ server['stats'][0]['desc'] }} <br />
{% if server['stats']['desc'] != 'False' %}
{{ server['stats']['desc'] }} <br />
{% end %}
{% if server['stats'][0]['version'] != 'False' %}
{{ server['stats'][0]['version'] }}
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% end %}
</td>
<td>
{% if server['stats'][0]['running'] %}
{% if server['stats']['running'] %}
<i class="fas fa-thumbs-up"></i> <span class="text-success">Online</span>
{% else %}
<i class="fas fa-thumbs-down"></i> <span class="text-danger">Offline</span>

View File

@ -4,9 +4,9 @@
<div class="card-body pt-3 pb-3">
<div class="row">
<div class="col-sm-3 mr-2">
{% if data['server_stats'][0]['running'] %}
{% if data['server_stats']['running'] %}
<b>Server Status:</b> <span class="text-success">Online</span><br />
<b>Server Started:</b> <span id="started">{{ data['server_stats'][0]['started'] }} (Server Time)</span><br />
<b>Server Started:</b> <span id="started">{{ data['server_stats']['started'] }} (Server Time)</span><br />
<b>Server Uptime:</b> <span id="uptime">Error Calculating</span>
{% else %}
<b>Server Status:</b> <span class="text-danger">Offline</span><br />
@ -16,19 +16,19 @@
</div>
<div class="col-sm-3 mr-2">
<b>CPU:</b> {{ data['server_stats'][0]['cpu'] }}% <br />
<b>Mem:</b> {{ data['server_stats'][0]['mem'] }} <br />
{% if data['server_stats'][0]['int_ping_results'] %}
<b>Players:</b> {{ data['server_stats'][0]['online'] }} / {{ data['server_stats'][0]['max'] }}<br />
<b>CPU:</b> {{ data['server_stats']['cpu'] }}% <br />
<b>Mem:</b> {{ data['server_stats']['mem'] }} <br />
{% if data['server_stats']['int_ping_results'] %}
<b>Players:</b> {{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}<br />
{% else %}
<b>Players:</b> 0/0<br />
{% end %}
</div>
<div class="col-sm-3 mr-2">
{% if data['server_stats'][0]['version'] != 'False' %}
<b>Server:</b> {{ data['server_stats'][0]['version'] }} <br />
<b>Desc:</b> {{ data['server_stats'][0]['desc'] }} <br />
{% if data['server_stats']['version'] != 'False' %}
<b>Server:</b> {{ data['server_stats']['version'] }} <br />
<b>Desc:</b> {{ data['server_stats']['desc'] }} <br />
{% else %}
<b>Server:</b> Unable To Connect <br />
<b>Desc:</b> Unable To Connect <br />
@ -86,9 +86,9 @@
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') }}';
console.log('88', '{{ data['server_stats']['started'] }}');
{% if data['server_stats']['started'] != 'False' %}
startedUTC = '{{ (datetime.datetime.strptime(data['server_stats']['started'], '%Y-%m-%d %H:%M:%S') - datetime.timedelta(seconds=-time.timezone)).strftime('%Y-%m-%d %H:%M:%S') }}';
{% end %}
console.log('utc', startedUTC);
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
@ -105,7 +105,7 @@
}
let nowServerTime = '{{ data['time'] }}';
let startedServerTime = '{{ data['server_stats'][0]['started'] }}';
let startedServerTime = '{{ data['server_stats']['started'] }}';
if (uptime != null && started != null) {

View File

@ -15,9 +15,9 @@
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Server Details - {{ data['server_stats'][0]['server_id']['server_name'] }}
Server Details - {{ data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats'][0]['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
@ -34,31 +34,31 @@
<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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</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=admin_controls" role="tab" aria-selected="true">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<i class="fas fa-users"></i>Player Controls</a>
</li>

View File

@ -0,0 +1,247 @@
{% 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']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['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']['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']['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']['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 active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="true">
<i class="fas fa-save"></i>Backup</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="false">
<i class="fas fa-cogs"></i>Config</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="false">
<i class="fas fa-users"></i>Player Controls</a>
</li>
</ul>
<div class="row">
<div class="col-md-6 col-sm-12">
<form class="forms-sample" method="post" action="/panel/server_backup">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="backup">
<div class="form-group">
<a href="/panel/backup_now?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary" onclick="backup_started()">Backup Now!</a>
</div>
<div class="form-group">
<label for="server_name">Storage Location <small class="text-muted ml-1"> - Where do you want to store backups?</small> </label>
<input type="text" class="form-control" name="backup_path" id="backup_path" value="{{ data['server_stats']['server_id']['backup_path'] }}" placeholder="Backup Path" >
</div>
<div class="form-group">
<label for="server_path">Max Backups <small class="text-muted ml-1"> - Crafty will not store more than n backups, deleting the oldest (enter 0 to keep all)</small> </label>
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="Max Backups" >
</div>
<div class="form-group">
<label for="superuser" class="form-check-label ml-4 mb-4">
{% if data['backup_config']['auto_enabled'] %}
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" checked="" value="1" >Auto-backup at 12:00 AM?
{% else %}
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" value="1" >Auto-backup at 12:00 AM?
{% 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">Current Backups</h4>
</div>
</div>
<div class="text-center">
<table class="table table-responsive dataTable" id="backup_table">
<thead>
<tr>
<th width="10%">Download</th>
<th>Path</th>
<th width="20%">Size</th>
<th width="10%">Delete</th>
</tr>
</thead>
<tbody>
{% for backup in data['backup_list'] %}
<tr>
<td>
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary">
<i class="fas fa-download" aria-hidden="true"></i>
Download
</a>
</td>
<td>{{ backup['path'] }}</td>
<td>{{ backup['size'] }}</td>
<td>
<button data-file="{{ backup['path'] }}" class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i>
Delete
</button>
</td>
</tr>
{% end %}
</tbody>
</table>
</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;
}
function backup_started(time='5-10') {
bootbox.alert({
message: "A backup task has been started.",
backdrop: true
});
}
function del_backup(filename, id){
var token = getCookie("_xsrf")
data_to_send = { file_name :filename}
console.log('Sending Command to delete file: ' + filename)
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/del_file?server_id='+id,
data: data_to_send,
success: function(data){
location.reload();
},
});
}
$( document ).ready(function() {
console.log( "ready!" );
$("#backup_config_box").hide();
$("#backup_save_note").hide();
$("#show_config").click(function () {
$("#backup_config_box").toggle();
$('#backup_button').hide();
$('#backup_save_note').show();
$('#backup_data').hide();
});
$('#backup_table').DataTable({
"order": [[ 1, "desc" ]],
"paging":true,
"lengthChange": false,
"searching": true,
"ordering": true,
"info": true,
"autoWidth": false,
"responsive": true,
});
$( ".del_button" ).click(function() {
var file_to_del = $(this).data("file");
console.log("file to delete is" + file_to_del);
bootbox.confirm({
title: "Destroy backup " + file_to_del + "?",
message: "Do you want to delete this file? This cannot be undone.",
buttons: {
cancel: {
label: '<i class="fas fa-times"></i> Cancel'
},
confirm: {
label: '<i class="fas fa-check"></i> Confirm'
}
},
callback: function (result) {
console.log(result);
if (result == true) {
del_backup(file_to_del, {{ data['server_stats']['server_id']['server_id'] }} );
}
}
});
});
});
</script>
{% end %}

View File

@ -15,9 +15,9 @@
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Server Details - {{ data['server_stats'][0]['server_id']['server_name'] }}
Server Details - {{ data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats'][0]['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
@ -34,31 +34,31 @@
<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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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 active" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="true">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&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/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<i class="fas fa-users"></i>Player Controls</a>
</li>
@ -68,62 +68,62 @@
<div class="col-md-6 col-sm-12">
<form class="forms-sample" method="post" action="/panel/server_detail">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats'][0]['server_id']['server_id'] }}">
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="server_name">Server Name <small class="text-muted ml-1"> - What you wish to call this server</small> </label>
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats'][0]['server_id']['server_name'] }}" placeholder="Server Name" >
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="Server Name" >
</div>
<div class="form-group">
<label for="server_path">Server Path <small class="text-muted ml-1"> - Absolute full path (not including executable)</small> </label>
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats'][0]['server_id']['path'] }}" placeholder="Server Path" >
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="Server Path" >
</div>
<div class="form-group">
<label for="log_path">Server Log Location <small class="text-muted ml-1"> - Absolute full path to the log file</small> </label>
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats'][0]['server_id']['log_path'] }}" placeholder="Server Log" >
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="Server Log" >
</div>
<div class="form-group">
<label for="executable">Server Executable <small class="text-muted ml-1"> - Just the executable file</small> </label>
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats'][0]['server_id']['executable'] }}" placeholder="Server Executable" >
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="Server Executable" >
</div>
<div class="form-group">
<label for="execution_command">Server Execution Command <small class="text-muted ml-1"> - What will be launched in a hidden terminal</small> </label>
<input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats'][0]['server_id']['execution_command'] }}" placeholder="Server Execution Command" >
<input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats']['server_id']['execution_command'] }}" placeholder="Server Execution Command" >
</div>
<div class="form-group">
<label for="stop_command">Server Stop Command <small class="text-muted ml-1"> - Command to send the program to stop it</small> </label>
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats'][0]['server_id']['stop_command'] }}" placeholder="Server Stop Command" >
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="Server Stop Command" >
</div>
<div class="form-group">
<label for="auto_start_delay">Server Autostart Delay <small class="text-muted ml-1"> - Delay before auto starting (if enabled below)</small> </label>
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats'][0]['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" >
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" >
</div>
<div class="form-group">
<label for="server_ip">Server IP <small class="text-muted ml-1"> - IP Crafty should connect to for stats (Try a real ip instead of 127.0.0.1 if you have issues)</small> </label>
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats'][0]['server_id']['server_ip'] }}">
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}">
</div>
<div class="form-group">
<label for="server_port">Server Port <small class="text-muted ml-1"> - Port Crafty should connect to for stats</small> </label>
<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']['server_id']['server_port'] }}" step="1" max="65566" min="1" >
</div>
<div class="form-group">
<label for="logs_delete_after">Remove Old Logs After <small class="text-muted ml-1"> - How many days will a log file has to be old to get deleted (0 is off)</small> </label>
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats'][0]['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" >
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" >
</div>
<div class="form-check-flat">
<label for="auto_start" class="form-check-label ml-4 mb-4">
{% if data['server_stats'][0]['server_id']['auto_start'] %}
{% if data['server_stats']['server_id']['auto_start'] %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked="" value="1">Server Auto Start
{% else %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1">Server Auto Start
@ -131,7 +131,7 @@
</label>
<label for="crash_detection" class="form-check-label ml-4 mb-4">
{% if data['server_stats'][0]['server_id']['crash_detection'] %}
{% if data['server_stats']['server_id']['crash_detection'] %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" checked="" value="1">Server Crash Detection
{% else %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" value="1" >Server Crash Detection
@ -170,11 +170,11 @@
</div>
</div>
<div class="text-center">
{% if data['server_stats'][0]['running'] %}
{% if data['server_stats']['running'] %}
<a class="btn btn-sm btn-danger disabled">Delete Server</a><br />
<small>Please stop the server before deleting it</small>
{% else %}
<a href="/panel/remove_server?id={{ data['server_stats'][0]['server_id']['server_id'] }}" class="btn btn-sm btn-danger">Delete Server</a>
<a href="/panel/remove_server?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-sm btn-danger">Delete Server</a>
{% end %}
</div>

View File

@ -15,9 +15,9 @@
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Server Details - {{ data['server_stats'][0]['server_id']['server_name'] }}
Server Details - {{ data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats'][0]['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
@ -34,31 +34,31 @@
<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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&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/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<i class="fas fa-users"></i>Player Controls</a>
</li>
@ -329,7 +329,7 @@
setFileName(event.target.innerText);
$.ajax({
type: 'GET',
url: '/ajax/get_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}&file_path=' + encodeURIComponent(filePath),
url: '/ajax/get_file?id={{ data['server_stats']['server_id']['server_id'] }}&file_path=' + encodeURIComponent(filePath),
dataType: 'text',
success: function (data) {
console.log('Got File Contents From Server');
@ -393,7 +393,7 @@
$.ajax({
type: "PUT",
headers: {'X-XSRFToken': token},
url: '/ajax/save_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/save_file?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
file_contents: text,
file_path: filePath
@ -410,7 +410,7 @@
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/create_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/create_file?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
file_parent: parent,
file_name: name
@ -428,7 +428,7 @@
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/create_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/create_dir?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
dir_parent: parent,
dir_name: name
@ -446,7 +446,7 @@
$.ajax({
type: "PUT",
headers: {'X-XSRFToken': token},
url: '/ajax/rename_item?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/rename_item?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
item_path: path,
new_item_name: name
@ -465,7 +465,7 @@
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/del_file?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
file_path: path
},
@ -482,7 +482,7 @@
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/del_dir?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
dir_path: path
},
@ -497,7 +497,7 @@
function getTreeView() {
$.ajax({
type: "GET",
url: '/ajax/get_tree?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/get_tree?id={{ data['server_stats']['server_id']['server_id'] }}',
dataType: 'text',
success: function(data){
console.log("got response:");

View File

@ -15,9 +15,9 @@
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Server Details - {{ data['server_stats'][0]['server_id']['server_name'] }}
Server Details - {{ data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats'][0]['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
@ -35,31 +35,31 @@
<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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="false">
<i class="fas fa-terminal"></i>Terminal</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=logs" role="tab" aria-selected="true">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="true">
<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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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="false">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="false">
<i class="fas fa-cogs"></i>Config</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<i class="fas fa-users"></i>Player Controls</a>
</li>
@ -91,7 +91,7 @@
if( !$("#stop_scroll").is(':checked')){
$.ajax({
type: 'GET',
url: '/ajax/server_log?id={{ data['server_stats'][0]['server_id']['server_id'] }}&full=1',
url: '/ajax/server_log?id={{ data['server_stats']['server_id']['server_id'] }}&full=1',
dataType: 'text',
success: function (data) {
console.log('Got Log From Server')

View File

@ -15,9 +15,9 @@
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Server Details - {{ data['server_stats'][0]['server_id']['server_name'] }}
Server Details - {{ data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats'][0]['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
@ -35,31 +35,31 @@
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item term-nav-item">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="true">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="true">
<i class="fas fa-file-signature"></i>Terminal</a>
</li>
<li class="nav-item term-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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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 term-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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
<i class="fas fa-clock"></i>Schedule</a>
</li>
<li class="nav-item term-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">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
<i class="fas fa-save"></i>Backup</a>
</li>
<li class="nav-item term-nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['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 term-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="false">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="false">
<i class="fas fa-cogs"></i>Config</a>
</li>
<li class="nav-item term-nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
<i class="fas fa-users"></i>Player Controls</a>
</li>
<li class="nav-item term-nav-item">
@ -128,7 +128,7 @@
// Convert running to lower case (example: 'True' converts to 'true') and
// then to boolean via JSON.parse()
let online = JSON.parse('{{ data['server_stats'][0]['running'] }}'.toLowerCase());
let online = JSON.parse('{{ data['server_stats']['running'] }}'.toLowerCase());
let startBtn = document.querySelector('#start-btn');
let restartBtn = document.querySelector('#restart-btn');
@ -144,13 +144,13 @@
stopBtn.setAttribute('disabled', 'disabled');
}
let server_id = '{{ data['server_stats'][0]['server_id']['server_id'] }}';
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
function get_server_log(){
if( !$("#stop_scroll").is(':checked')){
$.ajax({
type: 'GET',
url: '/ajax/server_log?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/server_log?id={{ data['server_stats']['server_id']['server_id'] }}',
dataType: 'text',
success: function (data) {
console.log('Got Log From Server')
@ -216,7 +216,7 @@
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/send_command?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
url: '/ajax/send_command?id={{ data['server_stats']['server_id']['server_id'] }}',
data: data_to_send,
success: function(data){
console.log("got response:");

37
main.py
View File

@ -10,8 +10,9 @@ from app.classes.shared.console import console
from app.classes.shared.helpers import helper
from app.classes.shared.models import installer
from app.classes.shared.controller import controller
from app.classes.shared.tasks import TasksManager
from app.classes.shared.controller import Controller
from app.classes.shared.cmd import MainPrompt
@ -56,7 +57,6 @@ def setup_logging(debug=True):
""" Our Main Starter """
if __name__ == '__main__':
parser = argparse.ArgumentParser("Crafty Controller - A Server Management System")
parser.add_argument('-i', '--ignore',
@ -94,9 +94,14 @@ if __name__ == '__main__':
fresh_install = installer.is_fresh_install()
if fresh_install:
console.debug("Fresh install detected")
installer.create_tables()
installer.default_settings()
else:
console.debug("Existing install detected")
installer.check_schema_version()
<<<<<<< HEAD
# init servers
logger.info("Initializing all servers defined")
console.info("Initializing all servers defined")
@ -107,15 +112,31 @@ if __name__ == '__main__':
# now the tables are created, we can load the tasks_manger
tasks_manager = TasksManager()
=======
# now the tables are created, we can load the tasks_manger and server controller
controller = Controller()
tasks_manager = TasksManager(controller)
>>>>>>> backups-and-stuff
tasks_manager.start_webserver()
tasks_manager.start_scheduler()
# slowing down reporting just for a 1/2 second so messages look cleaner
time.sleep(.5)
<<<<<<< HEAD
=======
# init servers
logger.info("Initializing all servers defined")
console.info("Initializing all servers defined")
controller.init_all_servers()
servers = controller.list_defined_servers()
>>>>>>> backups-and-stuff
# start stats logging
tasks_manager.start_stats_recording()
# once the controller is up and stats are logging, we can kick off the scheduler officially
tasks_manager.start_scheduler()
# refresh our cache and schedule for every 12 hoursour cache refresh for serverjars.com
tasks_manager.serverjar_cache_refresher()
@ -123,6 +144,7 @@ if __name__ == '__main__':
tasks_manager.start_main_kill_switch_watcher()
Crafty = MainPrompt(tasks_manager)
<<<<<<< HEAD
if not args.daemon:
Crafty.cmdloop()
else:
@ -143,3 +165,12 @@ if __name__ == '__main__':
if tasks_manager.get_main_thread_run_status():
sys.exit(0)
time.sleep(1)
=======
Crafty.cmdloop()
# our main loop - eventually a shell
# while True:
# if tasks_manager.get_main_thread_run_status():
# sys.exit(0)
# time.sleep(1)
>>>>>>> backups-and-stuff