Merge branch 'pretzel' into xithical

This commit is contained in:
Scott R 2021-08-13 23:07:26 -05:00
commit 2319d69417
18 changed files with 724 additions and 86 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -4,6 +4,8 @@ import logging
import sys
import yaml
import asyncio
import shutil
import tempfile
import zipfile
from distutils import dir_util
@ -110,10 +112,15 @@ class Controller:
@staticmethod
def list_authorized_servers(userId):
#servers = db_helper.get_authorized_servers(userId)
servers = db_helper.get_authorized_servers_from_roles(userId)
servers = db_helper.get_authorized_servers(userId)
server_list = []
for item in servers:
server_list.append(item)
role_servers = db_helper.get_authorized_servers_from_roles(userId)
for item in role_servers:
server_list.append(item)
logger.debug("servers list = {}".format(servers))
return servers
return server_list
def get_server_data(self, server_id):
for s in self.servers_list:
@ -276,8 +283,32 @@ class Controller:
if helper.check_file_perms(zip_path):
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
tempDir = tempfile.mkdtemp()
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(new_server_dir)
zip_ref.extractall(tempDir)
test = zip_ref.filelist[1].filename
path_list = test.split('/')
root_path = path_list[0]
if len(path_list) > 1:
for i in range(len(path_list)-2):
root_path = os.path.join(root_path, path_list[i+1])
full_root_path = os.path.join(tempDir, root_path)
has_properties = False
for item in os.listdir(full_root_path):
if str(item) == 'server.properties':
has_properties = True
try:
shutil.move(os.path.join(full_root_path, item), os.path.join(new_server_dir, item))
except Exception as ex:
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
if not has_properties:
logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port)))
with open(os.path.join(new_server_dir, "server.properties"), "w") as f:
f.write("server-port={}".format(port))
f.close()
zip_ref.close()
else:
return "false"
@ -313,7 +344,7 @@ class Controller:
return new_id
def remove_server(self, server_id):
def remove_server(self, server_id, files):
counter = 0
for s in self.servers_list:
@ -330,7 +361,8 @@ class Controller:
if running:
self.stop_server(server_id)
if files:
shutil.rmtree(db_helper.get_server_data_by_id(server_id)['path'])
# remove the server from the DB
db_helper.remove_server(server_id)

View File

@ -12,6 +12,7 @@ import logging
import html
import zipfile
import pathlib
import shutil
from datetime import datetime
from socket import gethostname
@ -553,4 +554,39 @@ class Helpers:
return json.loads(content)
helper = Helpers()
@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, '..')))
@staticmethod
def copy_files(source, dest):
if os.path.isfile(source):
shutil.copyfile(source, dest)
logger.info("Copying jar %s to %s", source, dest)
else:
logger.info("Source jar does not exist.")
@staticmethod
def download_file(executable_url, jar_path):
try:
r = requests.get(executable_url, timeout=5)
except Exception as ex:
logger.error("Could not download executable: %s", ex)
return False
if r.status_code != 200:
logger.error("Unable to download file from %s", executable_url)
return False
try:
open(jar_path, "wb").write(r.content)
except Exception as e:
logger.error("Unable to finish executable download. Error: %s", e)
return False
return True
helper = Helpers()

View File

@ -122,6 +122,7 @@ class Servers(BaseModel):
auto_start_delay = IntegerField(default=10)
crash_detection = BooleanField(default=0)
stop_command = CharField(default="stop")
executable_update_url = CharField(default="")
server_ip = CharField(default="127.0.0.1")
server_port = IntegerField(default=25565)
logs_delete_after = IntegerField(default=0)
@ -129,6 +130,16 @@ 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')
@ -156,6 +167,7 @@ class Server_Stats(BaseModel):
players = CharField(default="")
desc = CharField(default="Unable to Connect")
version = CharField(default="")
updating = BooleanField(default=False)
class Meta:
@ -220,6 +232,7 @@ class db_builder:
Users,
Roles,
User_Roles,
User_Servers,
Host_Stats,
Webhooks,
Servers,
@ -334,6 +347,7 @@ class db_shortcuts:
def remove_server(server_id):
with database.atomic():
Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
User_Servers.delete().where(User_Servers.server_id == server_id).execute()
Servers.delete().where(Servers.server_id == server_id).execute()
@staticmethod
@ -358,7 +372,37 @@ class db_shortcuts:
server_data.append(db_helper.get_server_data_by_id(u.server_id))
return server_data
@staticmethod
def get_all_authorized_servers(user_id):
user_servers = User_Servers.select().where(User_Servers.user_id == user_id)
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
server_data = []
roles_list = []
role_server = []
server_data = []
for u in user_servers:
server_data.append(db_helper.get_server_data_by_id(u.server_id))
for u in user_roles:
roles_list.append(db_helper.get_role(u.role_id))
for r in roles_list:
role_test = Role_Servers.select().where(Role_Servers.role_id == r.get('role_id'))
for t in role_test:
role_server.append(t)
for s in role_server:
found = False
for item in user_servers:
if s.server_id == item.server_id:
found = True
if not found:
server_data.append(db_helper.get_server_data_by_id(s.server_id))
return server_data
@staticmethod
def get_authorized_servers_from_roles(user_id):
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
@ -394,16 +438,44 @@ class db_shortcuts:
user_servers = User_Servers.select().where(User_Servers.user_id == user_id)
authorized_servers = []
server_data = []
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
roles_list = []
role_server = []
for u in user_servers:
authorized_servers.append(db_helper.get_server_data_by_id(u.server_id))
for u in user_roles:
roles_list.append(db_helper.get_role(u.role_id))
for r in roles_list:
role_test = Role_Servers.select().where(Role_Servers.role_id == r.get('role_id'))
for t in role_test:
role_server.append(t)
for s in role_server:
found = False
for item in user_servers:
if s.server_id == item.server_id:
found = True
if not found:
authorized_servers.append(db_helper.get_server_data_by_id(s.server_id))
for s in authorized_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)})
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)[0]})
return server_data
@staticmethod
def get_user_roles_names(user_id):
roles_list = []
roles = User_Roles.select().where(User_Roles.user_id == user_id)
for r in roles:
roles_list.append(db_helper.get_role(r.role_id)['role_name'])
return roles_list
@staticmethod
def get_authorized_servers_stats_from_roles(user_id):
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
@ -529,16 +601,34 @@ 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)
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
@staticmethod
def add_user_server(server_id, user_id):
servers = User_Servers.insert({User_Servers.server_id: server_id, User_Servers.user_id: user_id}).execute()
return servers
@staticmethod
def user_query(user_id):
user_query = Users.select().where(Users.user_id == user_id)
return user_query
@staticmethod
def user_role_query(user_id):
user_query = User_Roles.select().where(User_Roles.user_id == user_id)
query = Roles.select().where(Roles.role_id == -1)
for u in user_query:
query = Roles.select().where(Roles.role_id == u.role_id)
return query
@staticmethod
def get_user(user_id):
@ -555,7 +645,7 @@ class db_shortcuts:
superuser: False,
api_token: None,
roles: [],
servers: []
servers: [],
}
user = model_to_dict(Users.get(Users.user_id == user_id))
@ -581,9 +671,9 @@ class db_shortcuts:
elif key == "roles":
added_roles = user_data['roles'].difference(base_data['roles'])
removed_roles = base_data['roles'].difference(user_data['roles'])
#elif key == "servers":
# added_servers = user_data['servers'].difference(base_data['servers'])
# removed_servers = base_data['servers'].difference(user_data['servers'])
elif key == "servers":
added_servers = user_data['servers'].difference(base_data['servers'])
removed_servers = base_data['servers'].difference(user_data['servers'])
elif key == "regen_api":
if user_data['regen_api']:
up_data['api_token'] = db_shortcuts.new_api_token()
@ -600,10 +690,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)
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()
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()
@ -842,6 +932,15 @@ class db_shortcuts:
}
return conf
@staticmethod
def set_update(server_id, value):
try:
row = Server_Stats.select().where(Server_Stats.server_id == server_id)
except Exception as ex:
logger.error("Database entry not found. ".format(ex))
with database.atomic():
Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute()
@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()))

View File

@ -10,13 +10,13 @@ import threading
import schedule
import logging.config
import zipfile
import shutil
import zlib
from threading import Thread
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.shared.models import db_helper, Servers
from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__)
@ -44,11 +44,14 @@ class Server:
self.settings = None
self.updating = False
self.server_id = None
self.jar_update_url = None
self.name = None
self.is_crashed = False
self.restart_count = 0
self.crash_watcher_schedule = None
self.stats = stats
self.backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name="backup")
self.is_backingup = False
def reload_server_settings(self):
server_data = db_helper.get_server_data_by_id(self.server_id)
@ -113,11 +116,16 @@ class Server:
def start_server(self):
logger.info("Start command detected. Reloading settings from DB for server {}".format(self.name))
self.setup_server_run_command()
# fail safe in case we try to start something already running
if self.check_running():
logger.error("Server is already running - Cancelling Startup")
console.error("Server is already running - Cancelling Startup")
return False
if self.check_update():
logger.error("Server is updating. Terminating startup.")
return False
logger.info("Launching Server {} with command {}".format(self.name, self.server_command))
console.info("Launching Server {} with command {}".format(self.name, self.server_command))
@ -129,7 +137,17 @@ class Server:
logger.info("Linux Detected")
logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command))
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
try:
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
except Exception as ex:
msg = "Server {} failed to start with error code: {}".format(self.name, ex)
logger.error(msg)
websocket_helper.broadcast('send_start_error', {
'error': msg
})
return False
websocket_helper.broadcast('send_start_reload', {
})
self.is_crashed = False
self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
@ -320,21 +338,50 @@ class Server:
console.info("Removing old crash detection watcher thread")
schedule.clear(self.name)
def is_backup_running(self):
if self.is_backingup:
return True
else:
return False
def backup_server(self):
backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name="backup")
logger.info("Starting Backup Thread for server {}.".format(self.settings['server_name']))
#checks if the backup thread is currently alive for this server
if not self.is_backingup:
try:
backup_thread.start()
except Exception as ex:
logger.error("Failed to start backup: {}".format(ex))
return False
else:
logger.error("Backup is already being processed for server {}. Canceling backup request".format(self.settings['server_name']))
return False
logger.info("Backup Thread started for server {}.".format(self.settings['server_name']))
def a_backup_server(self):
logger.info("Starting server {} (ID {}) backup".format(self.name, self.server_id))
self.is_backingup = True
conf = db_helper.get_backup_config(self.server_id)
try:
backup_filename = "{}/{}".format(conf['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
backup_filename = "{}/{}.zip".format(self.settings['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))
shutil.make_archive(backup_filename, 'zip', self.server_path)
helper.zip_directory(backup_filename, self.server_path)
while len(self.list_backups()) > conf["max_backups"]:
backup_list = self.list_backups()
oldfile = backup_list[0]
oldfile_path = "{}/{}".format(conf['backup_path'], oldfile['path'])
logger.info("Removing old backup '{}'".format(oldfile['path']))
os.remove(oldfile_path)
backup_path = self.settings['backup_path']
old_file_name = oldfile['path']
back_old_file = os.path.join(backup_path, old_file_name)
logger.info("Removing old backup '{}'".format(oldfile))
os.remove(back_old_file)
self.is_backingup = False
logger.info("Backup of server: {} completed".format(self.name))
return
except:
logger.exception("Failed to create backup of server {} (ID {})".format(self.name, self.server_id))
self.is_backingup = False
return
def list_backups(self):
conf = db_helper.get_backup_config(self.server_id)
@ -343,3 +390,89 @@ class Server:
return [{"path": os.path.relpath(f['path'], start=conf['backup_path']), "size": f["size"]} for f in files]
else:
return []
def jar_update(self):
db_helper.set_update(self.server_id, True)
update_thread = threading.Thread(target=self.a_jar_update, daemon=True, name="exe_update")
update_thread.start()
def check_update(self):
server_stats = db_helper.get_server_stats_by_id(self.server_id)
if server_stats['updating']:
return True
else:
return False
def a_jar_update(self):
error = False
wasStarted = "-1"
self.backup_server()
#checks if server is running. Calls shutdown if it is running.
if self.check_running():
wasStarted = True
logger.info("Server with PID {} is running. Sending shutdown command".format(self.PID))
self.stop_threaded_server()
else:
wasStarted = False
if len(websocket_helper.clients) > 0:
# There are clients
self.check_update()
message = '<a data-id="'+str(self.server_id)+'" class=""> UPDATING...</i></a>'
websocket_helper.broadcast('update_button_status', {
'isUpdating': self.check_update(),
'server_id': self.server_id,
'wasRunning': wasStarted,
'string': message
})
backup_dir = os.path.join(self.settings['path'], 'crafty_executable_backups')
#checks if backup directory already exists
if os.path.isdir(backup_dir):
backup_executable = os.path.join(backup_dir, 'old_server.jar')
else:
logger.info("Executable backup directory not found for Server: {}. Creating one.".format(self.name))
os.mkdir(backup_dir)
backup_executable = os.path.join(backup_dir, 'old_server.jar')
if os.path.isfile(backup_executable):
#removes old backup
logger.info("Old backup found for server: {}. Removing...".format(self.name))
os.remove(backup_executable)
logger.info("Old backup removed for server: {}.".format(self.name))
else:
logger.info("No old backups found for server: {}".format(self.name))
current_executable = os.path.join(self.settings['path'], self.settings['executable'])
#copies to backup dir
helper.copy_files(current_executable, backup_executable)
#boolean returns true for false for success
downloaded = helper.download_file(self.settings['executable_update_url'], current_executable)
while db_helper.get_server_stats_by_id(self.server_id)['updating']:
if downloaded and not self.is_backingup:
logger.info("Executable updated successfully. Starting Server")
db_helper.set_update(self.server_id, False)
if len(websocket_helper.clients) > 0:
# There are clients
self.check_update()
websocket_helper.broadcast('notification', "Executable update finished for " + self.name)
time.sleep(3)
websocket_helper.broadcast('update_button_status', {
'isUpdating': self.check_update(),
'server_id': self.server_id,
'wasRunning': wasStarted
})
websocket_helper.broadcast('notification', "Executable update finished for "+self.name)
db_helper.add_to_audit_log_raw('Alert', '-1', self.server_id, "Executable update finished for "+self.name, self.settings['server_ip'])
if wasStarted:
self.start_server()
elif not downloaded and not self.is_backingup:
time.sleep(5)
db_helper.set_update(self.server_id, False)
websocket_helper.broadcast('notification',
"Executable update failed for " + self.name + ". Check log file for details.")
logger.error("Executable download failed.")
pass

View File

@ -5,6 +5,7 @@ import time
import logging
import threading
import asyncio
import shutil
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
@ -110,6 +111,18 @@ class TasksManager:
elif command == "backup_server":
svr.backup_server()
elif command == "update_executable":
svr.jar_update()
elif command == "delete_server":
logger.info(
"Removing server from panel for server: {}".format(c['server_id']['server_name']))
self.controller.remove_server(c['server_id']['server_id'], False)
elif command == "delete_server_files":
logger.info(
"Removing server and all associated files for server: {}".format(c['server_id']['server_name']))
self.controller.remove_server(c['server_id']['server_id'], True)
db_helper.mark_command_complete(c.get('command_id', None))
time.sleep(1)

View File

@ -210,7 +210,7 @@ class AjaxHandler(BaseHandler):
server_id = self.get_argument('id', None)
print(server_id)
console.warning("delete {} for server {}".format(file_path, server_id))
console.warning("delete {} for server {}".format(dir_path, server_id))
if not self.check_server_id(server_id, 'del_dir'): return False
else: server_id = bleach.clean(server_id)

View File

@ -7,6 +7,8 @@ import time
import datetime
import os
from tornado import iostream
from app.classes.shared.console import console
from app.classes.shared.models import Users, installer
from app.classes.web.base_handler import BaseHandler
@ -36,11 +38,11 @@ class PanelHandler(BaseHandler):
defined_servers = self.controller.list_defined_servers()
exec_user_role.add("Super User")
else:
defined_servers = self.controller.list_authorized_servers(exec_user_id)
logger.debug(exec_user['roles'])
for r in exec_user['roles']:
role = db_helper.get_role(r)
exec_user_role.add(role['role_name'])
defined_servers = db_helper.get_all_authorized_servers(exec_user_id)
page_data = {
# todo: make this actually pull and compare version data
@ -109,10 +111,9 @@ class PanelHandler(BaseHandler):
if exec_user['superuser'] == 1:
page_data['servers'] = db_helper.get_all_servers_stats()
else:
#page_data['servers'] = db_helper.get_authorized_servers_stats(exec_user_id)
ras = db_helper.get_authorized_servers_stats_from_roles(exec_user_id)
logger.debug("ASFR: {}".format(ras))
page_data['servers'] = ras
user_auth = db_helper.get_authorized_servers_stats(exec_user_id)
logger.debug("ASFR: {}".format(user_auth))
page_data['servers'] = user_auth
for s in page_data['servers']:
try:
@ -137,10 +138,10 @@ class PanelHandler(BaseHandler):
return
if exec_user['superuser'] != 1:
#if not db_helper.server_id_authorized(server_id, exec_user_id):
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
self.redirect("/panel/error?error=Invalid Server ID")
return False
if not db_helper.server_id_authorized(server_id, exec_user_id):
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
self.redirect("/panel/error?error=Invalid Server ID")
return False
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls']
@ -255,8 +256,43 @@ class PanelHandler(BaseHandler):
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()
auth_servers = {}
auth_role_servers = {}
users_list = []
role_users = {}
roles = db_helper.get_all_roles()
role_servers = []
user_roles = {}
for user in db_helper.get_all_users():
user_roles_list = db_helper.get_user_roles_names(user.user_id)
user_servers = db_helper.get_all_authorized_servers(user.user_id)
servers = []
for server in user_servers:
servers.append(server['server_name'])
new_item = {user.user_id: servers}
auth_servers.update(new_item)
data = {user.user_id: user_roles_list}
user_roles.update(data)
for role in roles:
role_servers = []
role = db_helper.get_role(role.role_id)
for serv_id in role['servers']:
role_servers.append(db_helper.get_server_data_by_id(serv_id)['server_name'])
data = {role['role_id']: role_servers}
auth_role_servers.update(data)
page_data['auth-servers'] = auth_servers
page_data['role-servers'] = auth_role_servers
page_data['user-roles'] = user_roles
if exec_user['superuser'] == 1:
page_data['users'] = db_helper.get_all_users()
page_data['roles'] = db_helper.get_all_roles()
else:
page_data['users'] = db_helper.user_query(exec_user['user_id'])
page_data['roles'] = db_helper.user_role_query(exec_user['user_id'])
for user in page_data['users']:
if user.user_id != exec_user['user_id']:
user.api_token = "********"
@ -283,22 +319,39 @@ class PanelHandler(BaseHandler):
page_data['roles_all'] = db_helper.get_all_roles()
page_data['servers'] = []
page_data['servers_all'] = self.controller.list_defined_servers()
page_data['role-servers'] = []
template = "panel/panel_edit_user.html"
elif page == "edit_user":
page_data['new_user'] = False
user_id = self.get_argument('id', None)
user_servers = db_helper.get_authorized_servers(user_id)
role_servers = db_helper.get_authorized_servers_from_roles(user_id)
page_role_servers = []
servers = set()
for server in user_servers:
flag = False
for rserver in role_servers:
if rserver['server_id'] == server['server_id']:
flag = True
if not flag:
servers.add(server['server_id'])
for server in role_servers:
page_role_servers.append(server['server_id'])
page_data['new_user'] = False
page_data['user'] = db_helper.get_user(user_id)
page_data['servers'] = db_helper.get_authorized_servers_stats_from_roles(user_id)
page_data['servers'] = servers
page_data['role-servers'] = page_role_servers
page_data['roles_all'] = db_helper.get_all_roles()
page_data['servers_all'] = self.controller.list_defined_servers()
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return
elif user_id is None:
if user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return
elif not exec_user['superuser']:
page_data['servers'] = []
page_data['role-servers'] = []
page_data['roles_all'] = []
page_data['servers_all'] = []
if exec_user['user_id'] != page_data['user']['user_id']:
page_data['user']['api_token'] = "********"
@ -332,6 +385,12 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/panel_config")
elif page == "add_role":
user_roles = {}
for user in db_helper.get_all_users():
user_roles_list = db_helper.get_user_roles_names(user.user_id)
user_servers = db_helper.get_all_authorized_servers(user.user_id)
data = {user.user_id: user_roles_list}
user_roles.update(data)
page_data['new_role'] = True
page_data['role'] = {}
page_data['role']['role_name'] = ""
@ -339,6 +398,8 @@ class PanelHandler(BaseHandler):
page_data['role']['created'] = "N/A"
page_data['role']['last_update'] = "N/A"
page_data['role']['servers'] = set()
page_data['user-roles'] = user_roles
page_data['users'] = db_helper.get_all_users()
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
@ -348,10 +409,20 @@ class PanelHandler(BaseHandler):
template = "panel/panel_edit_role.html"
elif page == "edit_role":
auth_servers = {}
user_roles = {}
for user in db_helper.get_all_users():
user_roles_list = db_helper.get_user_roles_names(user.user_id)
user_servers = db_helper.get_all_authorized_servers(user.user_id)
data = {user.user_id: user_roles_list}
user_roles.update(data)
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'] = self.controller.list_defined_servers()
page_data['user-roles'] = user_roles
page_data['users'] = db_helper.get_all_users()
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
@ -426,14 +497,17 @@ class PanelHandler(BaseHandler):
auto_start_delay = self.get_argument('auto_start_delay', '10')
server_ip = self.get_argument('server_ip', None)
server_port = self.get_argument('server_port', None)
executable_update_url = self.get_argument('executable_update_url', None)
auto_start = int(float(self.get_argument('auto_start', '0')))
crash_detection = int(float(self.get_argument('crash_detection', '0')))
logs_delete_after = int(float(self.get_argument('logs_delete_after', '0')))
subpage = self.get_argument('subpage', None)
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return
if not db_helper.server_id_authorized(server_id, exec_user_id):
if not db_helper.server_id_authorized_from_roles(server_id, exec_user_id):
self.redirect("/panel/error?error=Unauthorized access: invalid server id")
return
elif server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return
@ -454,6 +528,7 @@ class PanelHandler(BaseHandler):
Servers.server_ip: server_ip,
Servers.server_port: server_port,
Servers.auto_start: auto_start,
Servers.executable_update_url: executable_update_url,
Servers.crash_detection: crash_detection,
Servers.logs_delete_after: logs_delete_after,
}).where(Servers.server_id == server_id).execute()
@ -508,7 +583,18 @@ class PanelHandler(BaseHandler):
regen_api = int(float(self.get_argument('regen_api', '0')))
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
user_data = {
"username": username,
"password": password0,
}
db_helper.update_user(user_id, user_data=user_data)
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited user {} (UID:{}) password".format(username,
user_id),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
return
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
@ -536,17 +622,28 @@ class PanelHandler(BaseHandler):
if argument:
roles.add(role.role_id)
servers = set()
for server in self.controller.list_defined_servers():
argument = int(float(
bleach.clean(
self.get_argument('server_{}_access'.format(server['server_id']), '0')
)
))
if argument:
servers.add(server['server_id'])
user_data = {
"username": username,
"password": password0,
"enabled": enabled,
"regen_api": regen_api,
"roles": roles,
"servers": servers,
}
db_helper.update_user(user_id, user_data=user_data)
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited user {} (UID:{}) with roles {}".format(username, user_id, roles),
"Edited user {} (UID:{}) with roles {} and servers {}".format(username, user_id, roles, servers),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
@ -595,7 +692,11 @@ class PanelHandler(BaseHandler):
servers.add(server['server_id'])
user_id = db_helper.add_user(username, password=password0, enabled=enabled)
db_helper.update_user(user_id, {"roles":roles})
user_data = {
"roles": roles,
"servers": servers,
}
db_helper.update_user(user_id, user_data)
db_helper.add_to_audit_log(exec_user['user_id'],
"Added user {} (UID:{})".format(username, user_id),

View File

@ -199,6 +199,7 @@ class ServerHandler(BaseHandler):
server_type, server_version = server_parts
# todo: add server type check here and call the correct server add functions if not a jar
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
db_helper.add_user_server(new_server_id, exec_user_id)
db_helper.add_to_audit_log(exec_user_data['user_id'],
"created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,

View File

@ -219,6 +219,25 @@
var webSocket;
// {% end%}
if (webSocket) {
webSocket.on('send_start_error', function (start_error) {
var x = document.querySelector('.bootbox');
if(x){
x.remove()}
var x = document.querySelector('.modal-backdrop');
if(x){
x.remove()}
bootbox.alert(start_error.error);
});
}
if (webSocket) {
webSocket.on('send_start_reload', function (start_error) {
location.reload()
});
}
function warn(message) {
var closeEl = document.createElement('span');
var strongEL = document.createElement('strong');

View File

@ -123,14 +123,15 @@
</a>
</td>
<td class="actions_serverlist">
<td id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% 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;
{% elif server['stats']['updating']%}
<a data-id="{{server['server_data']['server_id']}}" class=""> UPDATING...</i></a>
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a>&nbsp;
{% end %}
</td>
@ -228,7 +229,11 @@ function send_command (server_id, command){
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){ location.reload(); }, 10000);
setTimeout(function(){
if (command != 'start_server'){
location.reload();
}
}, 10000);
}
});
@ -281,6 +286,21 @@ $( document ).ready(function() {
mem_percent.textContent = hostStats.mem_percent + '%';
});
}
if (webSocket) {
webSocket.on('update_button_status', function (updateButton) {
var id = 'controls';
var dataId = updateButton.server_id;
var string = updateButton.string
var id = id.concat(updateButton.server_id);
if (updateButton.isUpdating){
console.log(updateButton.isUpdating)
document.getElementById(id).innerHTML = string;
}
else{
window.location.reload()
}
});
}
$( ".clone_button" ).click(function() {
server_id = $(this).attr("data-id");

View File

@ -45,6 +45,7 @@
<th>Enabled</th>
<th>API Token</th>
<th>Allowed Servers</th>
<th>Assigned Roles</th>
<th>Edit</th>
</tr>
</thead>
@ -66,7 +67,16 @@
</td>
<td>{{ user.api_token }}</td>
<td>{{ [] }}</td>
<td class="server_list_{{user.user_id}}"><ul id="{{user.user_id}}">
{% for item in data['auth-servers'][user.user_id] %}
<li>{{item}}</li>
{% end %}
</ul></td>
<td class="role_list_{{user.user_id}}"><ul>
{% for item in data['user-roles'][user.user_id] %}
<li>{{item}}</li>
{% end %}
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr>
{% end %}
@ -95,6 +105,7 @@
<tr class="rounded">
<th>Role</th>
<th>Allowed Servers</th>
<th>Role Users</th>
<th>Edit</th>
</tr>
</thead>
@ -102,7 +113,21 @@
{% for role in data['roles'] %}
<tr>
<td>{{ role.role_name }}</td>
<td>{{ [] }}</td>
<td class="role_list_{{role.role_id}}"><ul id="{{role.role_id}}">
{% for item in data['role-servers'][role.role_id] %}
<li>{{item}}</li>
{% end %}
</ul></td>
<td><ul>
{% for user in data['users'] %}
{% for ruser in data['user-roles'][user.user_id] %}
{% if ruser == role.role_name %}
<li>{{ user.username }}</li>
{% end %}
{% end %}
{% end %}
</ul>
</td>
<td><a href="/panel/edit_role?id={{role.role_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr>
{% end %}

View File

@ -40,15 +40,14 @@
<div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_name'] }}&subpage=config" role="tab" aria-selected="true">
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_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/edit_role?id={{ data['role']['role_name'] }}&subpage=other" role="tab" aria-selected="false">
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a>
</li>
</ul>
<div class="row">
<div class="col-md-6 col-sm-12">
{% if data['new_role'] %}
@ -95,11 +94,24 @@
</div>
<button type="submit" class="btn btn-success mr-2">Save</button>
<button type="reset" class="btn btn-light">Cancel</button>
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light">Cancel</button>
</form>
</div>
<div class="col-md-6 col-sm-12">
</div>
<div class="col-md-3 col-sm-12">
<h6>Users Assigned to Role:</h6>
<ul>
{% for user in data['users'] %}
{% for ruser in data['user-roles'][user.user_id] %}
{% if ruser == data['role']['role_name'] %}
<li>{{ user.username }}</li>
{% end %}
{% end %}
{% end %}
</ul>
<br>
</div>
<div class="col-md-3 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">Role Config Area</h4>
@ -114,7 +126,9 @@
</blockquote>
</div>
</div>
<div class="text-center">
</div>
</div>
<div class="text-center">
{% if data['new_role'] %}
<a class="btn btn-sm btn-danger disabled">Delete Role</a><br />
<small>You cannot delete something that does not yet exist</small>
@ -123,8 +137,6 @@
{% end %}
</div>
</div>
</div>
</div>
</div>

View File

@ -76,7 +76,7 @@
<label for="password1">Repeat Password <small class="text-muted ml-1"></small> </label>
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
</div>
{% if len(data['servers_all']) > 0 %}
<div class="form-group">
<label for="role_membership">Roles <small class="text-muted ml-1"> - the roles this user is a member of</small> </label>
<div class="table-responsive">
@ -106,7 +106,6 @@
</table>
</div>
</div>
<div class="form-group">
<label for="server_membership">Servers <small class="text-muted ml-1"> - servers this user is allowed to access </small> </label>
<div class="table-responsive">
@ -123,9 +122,11 @@
<td>{{ server['server_name'] }}</td>
<td>
{% if server['server_id'] in data['servers'] %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" disabled>
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="1">
{% elif server['server_id'] in data['role-servers'] %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="" disabled>
{% else %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" disabled>
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" value="1">
{% end %}
</td>
</tr>
@ -136,6 +137,7 @@
</div>
</div>
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
{% if data['user']['enabled'] %}
@ -162,9 +164,9 @@
</label>
</div>
{% end %}
<button type="submit" class="btn btn-success mr-2">Save</button>
<button type="reset" class="btn btn-light">Cancel</button>
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light">Cancel</button>
</form>
</div>

View File

@ -106,13 +106,18 @@
<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="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc') }}</small> </label>
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL') }}" >
</div>
<div class="form-group">
<label for="server_ip">{{ translate('serverConfig', 'serverPort') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc') }}</small> </label>
<label for="server_ip">{{ translate('serverConfig', 'serverIP') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc') }}</small> </label>
<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">{{ translate('serverConfig', 'serverIP') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc') }}</small> </label>
<label for="server_port">{{ translate('serverConfig', 'serverPort') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc') }}</small> </label>
<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>
@ -159,10 +164,12 @@
</div>
<div class="text-center">
{% if data['server_stats']['running'] %}
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig', 'update') }}</button>
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer') }}</a><br />
<small>{{ translate('serverConfig', 'stopBeforeDeleting') }}</small>
{% else %}
<a href="/panel/remove_server?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer') }}</a>
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig', 'update') }}</button>
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer') }}</button>
{% end %}
</div>
@ -196,6 +203,105 @@
});
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
function send_command (server_id, command){
<!-- this getCookie function is in base.html-->
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/server/command?command=' + command + '&id=' + server_id,
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){ location.reload(); }, 10000);
}
});
if(command != "delete_server" && command != "delete_server_files"){
bootbox.alert({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingRequest") %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientUpdate") %} </div>'
});
}
}
function deleteServer (){
path = "{{data['server_stats']['server_id']['path']}}";
name = "{{data['server_stats']['server_id']['server_name']}}";
bootbox.confirm({
size: "",
title: "{% raw translate('serverConfig', 'deleteFilesQuestion') %}",
closeButton: false,
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage') %}",
buttons: {
confirm: {
label: "{{ translate('serverConfig', 'yesDeleteFiles') }}",
className: 'btn-danger',
},
cancel: {
label: "{{ translate('serverConfig', 'noDeleteFiles') }}",
className: 'btn-link',
}
},
callback: function(result) {
if (!result){
send_command(server_id, 'delete_server');
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete") %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDelete") %} </div>',
closeButton: false
})
return;}
else{
send_command(server_id, 'delete_server_files');
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete") %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDeleteFiles") %} </div>',
closeButton: false
})
}
}
});
}
function deleteConfirm (){
path = "{{data['server_stats']['server_id']['path']}}";
name = "{{data['server_stats']['server_id']['server_name']}}";
bootbox.confirm({
size: "",
title: "{% raw translate('serverConfig', 'deleteServerQuestion') %}",
closeButton: false,
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage') %}",
buttons: {
confirm: {
label: "{{ translate('serverConfig', 'yesDelete') }}",
className: 'btn-danger',
},
cancel: {
label: "{{ translate('serverConfig', 'noDelete') }}",
className: 'btn-link',
}
},
callback: function(result) {
if (!result){
return;
return;}
else{
deleteServer();
}
}
});
}
</script>

View File

@ -83,12 +83,19 @@
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand') }}</button>
</span>
</div>
<div class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0">
{% if data['server_stats']['updating']%}
<div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'updating') }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart') %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop') }}</button>
</div>
{% else %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="send_command(server_id, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start') }}</button>
<button onclick="send_command(server_id, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart') %}</button>
<button onclick="send_command(server_id, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop') }}</button>
</div>
{% end %}
</div>
@ -120,12 +127,27 @@
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){ location.reload(); }, 10000);
setTimeout(function(){
if (command != 'start_server'){
location.reload();
}
}, 10000);
}
});
}
if (webSocket) {
webSocket.on('update_button_status', function (updateButton) {
if (updateButton.isUpdating){
console.log(updateButton.isUpdating)
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating") }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart") %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop") }}</button>';
}
else{
window.location.reload()
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start") }}</button><button onclick="send_command(server_id, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart") %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop") }}</button>';
}
});
}
// Convert running to lower case (example: 'True' converts to 'true') and
// then to boolean via JSON.parse()
let online = JSON.parse('{{ data['server_stats']['running'] }}'.toLowerCase());

View File

@ -125,8 +125,9 @@
"sendCommand": "Send command",
"start": "Start",
"restart": "Restart",
"stop": "Stop"
},
"stop": "Stop",
"updating": "Updating..."
},
"serverPlayerManagement": {
"players": "Players",
"bannedPlayers": "Banned Players",
@ -199,7 +200,23 @@
"save": "Save",
"cancel": "Cancel",
"deleteServer": "Delete Server",
"stopBeforeDeleting": "Please stop the server before deleting it"
"stopBeforeDeleting": "Please stop the server before deleting it",
"exeUpdateURLDesc": "Direct Download URL for updates.",
"exeUpdateURL": "Server Executable Update URL",
"update": "Update Executable",
"bePatientUpdate": "Please be patient while we update the server. Download times can vary depending upon your internet speeds.<br /> This screen will refresh in a moment",
"sendingRequest": "Sending your request...",
"deleteServerQuestion": "Delete Server?",
"deleteServerQuestionMessage": "Are you sure you want to delete this server? After this there is no going back...",
"yesDelete": "Yes, delete",
"noDelete": "No, go back",
"deleteFilesQuestion": "Delete server files from machine?",
"deleteFilesQuestionMessage": "Would you like Crafty to delete all server files from the host machine?",
"yesDeleteFiles": "Yes, delete files",
"noDeleteFiles": "No, just remove from panel",
"sendingDelete": "Deleting Server",
"bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.",
"bePatientDeleteFiles" : "Please be patient while we remove your server from the Crafty panel and delete all files. This screen will close in a few moments."
},
"serverConfigHelp": {
"title": "Server Config Area",

View File

@ -23,4 +23,4 @@ termcolor==1.1.0
tornado==6.0.4
urllib3==1.25.10
webencodings==0.5.1
peewee_migrate==1.4.6