Merge branch 'pretzel-fixes' into 'dev'

Bedrock support/chain reaction tasks / server re-ordering

See merge request crafty-controller/crafty-commander!166
This commit is contained in:
Andrew 2022-02-27 12:46:40 +00:00
commit 2265adbf82
31 changed files with 1974 additions and 847 deletions

View File

@ -1,23 +1,17 @@
FROM python:alpine
FROM ubuntu:20.04
ENV DEBIAN_FRONTEND="noninteractive"
LABEL maintainer="Dockerfile created by Zedifus <https://gitlab.com/zedifus>"
# Security Patch for CVE-2021-44228
ENV LOG4J_FORMAT_MSG_NO_LOOKUPS=true
# Install Packages, Build Dependencies & Garbage Collect & Harden
# (Alpine Edge repo is needed because jre16 is new)
# Install Packages And Dependencies
COPY requirements.txt /commander/requirements.txt
RUN apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/latest-stable/community \
gcc musl-dev libffi-dev make rust cargo openssl-dev llvm11-libs \
openjdk8-jre-base openjdk11-jre-headless openjdk16-jre-headless openjdk17-jre-headless mariadb-dev \
&& pip3 install --no-cache-dir -r /commander/requirements.txt \
&& apk del --no-cache gcc musl-dev libffi-dev make rust cargo openssl-dev llvm11-libs \
&& rm -rf /sbin/apk \
&& rm -rf /etc/apk \
&& rm -rf /lib/apk \
&& rm -rf /usr/share/apk \
&& rm -rf /var/lib/apk
RUN apt update \
&& apt install -y gcc python3 python3-pip libmariadb-dev openjdk-8-jre-headless openjdk-11-jre-headless openjdk-16-jre-headless openjdk-17-jre-headless default-jre \
&& pip3 install --no-cache-dir -r /commander/requirements.txt
# Copy Source & copy default config from image
COPY ./ /commander

View File

@ -80,6 +80,10 @@ class Management_Controller:
def get_scheduled_task_model(schedule_id):
return management_helper.get_scheduled_task_model(schedule_id)
@staticmethod
def get_child_schedules(sch_id):
return management_helper.get_child_schedules(sch_id)
@staticmethod
def get_schedules_by_server(server_id):
return management_helper.get_schedules_by_server(server_id)

View File

@ -28,6 +28,7 @@ class Servers_Controller:
server_file: str,
server_log_file: str,
server_stop: str,
server_type: str,
server_port=25565):
return servers_helper.create_server(
name,
@ -38,6 +39,7 @@ class Servers_Controller:
server_file,
server_log_file,
server_stop,
server_type,
server_port)
@staticmethod
@ -137,6 +139,10 @@ class Servers_Controller:
def server_id_exists(server_id):
return servers_helper.server_id_exists(server_id)
@staticmethod
def get_server_type_by_id(server_id):
return servers_helper.get_server_type_by_id(server_id)
@staticmethod
def server_id_authorized(server_id_a, user_id):
user_roles = users_helper.user_role_query(user_id)

View File

@ -30,6 +30,14 @@ class Users_Controller:
def get_user_by_id(user_id):
return users_helper.get_user(user_id)
@staticmethod
def update_server_order(user_id, user_server_order):
users_helper.update_server_order(user_id, user_server_order)
@staticmethod
def get_server_order(user_id):
return users_helper.get_server_order(user_id)
@staticmethod
def user_query(user_id):
return users_helper.user_query(user_id)

View File

@ -165,3 +165,66 @@ def ping(ip, port):
return Server(json.loads(data))
finally:
sock.close()
# For the rest of requests see wiki.vg/Protocol
def ping_bedrock(ip, port):
def read_var_int():
i = 0
j = 0
while True:
try:
k = sock.recvfrom(1024)
except:
return False
if not k:
return 0
k = k[0]
i |= (k & 0x7f) << (j * 7)
j += 1
if j > 5:
raise ValueError('var_int too big')
if not k & 0x80:
return i
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(2)
try:
sock.connect((ip, port))
except:
print("in first except")
return False
try:
host = ip.encode('utf-8')
data = b'' # wiki.vg/Server_List_Ping
data += b'\x00' # packet ID
data += b'\x04' # protocol variant
data += struct.pack('>b', len(host)) + host
data += struct.pack('>H', port)
data += b'\x01' # next state
data = struct.pack('>b', len(data)) + data
sock.sendall(data + b'\x01\x00') # handshake + status ping
length = read_var_int() # full packet length
if length < 10:
if length < 0:
return False
else:
return False
try:
sock.recvfrom(1024) # packet type, 0 for pings
except:
return False
length = read_var_int() # string length
data = b''
while len(data) != length:
print("in while")
chunk = sock.recv(length - len(data))
if not chunk:
return False
data += chunk
logger.debug(f"Server reports this data on ping: {data}")
return Server(json.loads(data))
finally:
sock.close()

View File

@ -9,7 +9,7 @@ from app.classes.models.management import Host_Stats
from app.classes.models.servers import Server_Stats, servers_helper
from app.classes.shared.helpers import helper
from app.classes.minecraft.mc_ping import ping
from app.classes.minecraft.mc_ping import ping, ping_bedrock
logger = logging.getLogger(__name__)
@ -177,7 +177,10 @@ class Stats:
server_port = server['server_port']
logger.debug("Pinging {internal_ip} on port {server_port}")
int_mc_ping = ping(internal_ip, int(server_port))
if servers_helper.get_server_type_by_id(server_id) == 'minecraft-bedrock':
int_mc_ping = ping_bedrock(internal_ip, int(server_port))
else:
int_mc_ping = ping(internal_ip, int(server_port))
ping_data = {}
@ -223,7 +226,10 @@ class Stats:
server = s.get('server_name', f"ID#{server_id}")
logger.debug("Pinging server '{server}' on {internal_ip}:{server_port}")
int_mc_ping = ping(internal_ip, int(server_port))
if servers_helper.get_server_type_by_id(server_id) == 'minecraft-bedrock':
int_mc_ping = ping_bedrock(internal_ip, int(server_port))
else:
int_mc_ping = ping(internal_ip, int(server_port))
int_data = False
ping_data = {}
@ -286,7 +292,10 @@ class Stats:
logger.debug(f"Pinging server '{server.name}' on {internal_ip}:{server_port}")
int_mc_ping = ping(internal_ip, int(server_port))
if servers_helper.get_server_type_by_id(server_id) == 'minecraft-bedrock':
int_mc_ping = ping_bedrock(internal_ip, int(server_port))
else:
int_mc_ping = ping(internal_ip, int(server_port))
int_data = False
ping_data = {}

View File

@ -113,6 +113,8 @@ class Schedules(Model):
comment = CharField()
one_time = BooleanField(default=False)
cron_string = CharField(default="")
parent = IntegerField(null=True)
delay = IntegerField(default=0)
class Meta:
table_name = 'schedules'
@ -191,11 +193,14 @@ class helpers_management:
Audit_Log.log_msg: audit_msg,
Audit_Log.source_ip: source_ip
}).execute()
#todo make this user configurable
#deletes records when they're more than 100
ordered = Audit_Log.select().order_by(+Audit_Log.created)
for item in ordered:
if Audit_Log.select().count() > 300:
if not helper.get_setting('max_audit_entries'):
max_entries = 300
else:
max_entries = helper.get_setting('max_audit_entries')
if Audit_Log.select().count() > max_entries:
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute()
else:
return
@ -212,7 +217,12 @@ class helpers_management:
#deletes records when they're more than 100
ordered = Audit_Log.select().order_by(+Audit_Log.created)
for item in ordered:
if Audit_Log.select().count() > 300:
#configurable through app/config/config.json
if not helper.get_setting('max_audit_entries'):
max_entries = 300
else:
max_entries = helper.get_setting('max_audit_entries')
if Audit_Log.select().count() > max_entries:
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute()
else:
return
@ -230,7 +240,9 @@ class helpers_management:
comment=None,
enabled=True,
one_time=False,
cron_string='* * * * *'):
cron_string='* * * * *',
parent=None,
delay=0):
sch_id = Schedules.insert({
Schedules.server_id: server_id,
Schedules.action: action,
@ -241,7 +253,9 @@ class helpers_management:
Schedules.command: command,
Schedules.comment: comment,
Schedules.one_time: one_time,
Schedules.cron_string: cron_string
Schedules.cron_string: cron_string,
Schedules.parent: parent,
Schedules.delay: delay
}).execute()
return sch_id
@ -271,6 +285,14 @@ class helpers_management:
def get_schedules_by_server(server_id):
return Schedules.select().where(Schedules.server_id == server_id).execute()
@staticmethod
def get_child_schedules_by_server(schedule_id, server_id):
return Schedules.select().where(Schedules.server_id == server_id, Schedules.parent == schedule_id).execute()
@staticmethod
def get_child_schedules(schedule_id):
return Schedules.select().where(Schedules.parent == schedule_id)
@staticmethod
def get_schedules_all():
return Schedules.select().execute()

View File

@ -173,7 +173,10 @@ class Permissions_Servers:
else:
roles_list = users_helper.get_user_roles_id(user.user_id)
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
permissions_mask = role_server[0].permissions
try:
permissions_mask = role_server[0].permissions
except IndexError:
permissions_mask = '0' * len(server_permissions.get_permissions_list())
return permissions_mask
@staticmethod

View File

@ -43,6 +43,7 @@ class Servers(Model):
server_ip = CharField(default="127.0.0.1")
server_port = IntegerField(default=25565)
logs_delete_after = IntegerField(default=0)
type = CharField(default="minecraft-java")
class Meta:
table_name = "servers"
@ -99,6 +100,7 @@ class helper_servers:
server_file: str,
server_log_file: str,
server_stop: str,
server_type: str,
server_port=25565):
return Servers.insert({
Servers.server_name: name,
@ -112,7 +114,8 @@ class helper_servers:
Servers.log_path: server_log_file,
Servers.server_port: server_port,
Servers.stop_command: server_stop,
Servers.backup_path: backup_path
Servers.backup_path: backup_path,
Servers.type: server_type
}).execute()
@ -120,6 +123,11 @@ class helper_servers:
def get_server_obj(server_id):
return Servers.get_by_id(server_id)
@staticmethod
def get_server_type_by_id(server_id):
server_type = Servers.select().where(Servers.server_id == server_id).get()
return server_type.type
@staticmethod
def update_server(server_obj):
return server_obj.save()

View File

@ -42,6 +42,7 @@ class Users(Model):
lang = CharField(default="en_EN")
support_logs = CharField(default = '')
valid_tokens_from = DateTimeField(default=datetime.datetime.now)
server_order = CharField(default="")
class Meta:
table_name = "users"
@ -173,6 +174,14 @@ class helper_users:
if up_data:
Users.update(up_data).where(Users.user_id == user_id).execute()
@staticmethod
def update_server_order(user_id, user_server_order):
Users.update(server_order = user_server_order).where(Users.user_id == user_id).execute()
@staticmethod
def get_server_order(user_id):
return Users.select().where(Users.user_id == user_id)
@staticmethod
def get_super_user_list():
final_users = []

View File

@ -295,7 +295,8 @@ 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, backup_path, server_command, server_file, server_log_file, server_stop, port)
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop,
port, server_type='minecraft-java')
return new_id
@staticmethod
@ -351,7 +352,7 @@ class Controller:
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
server_log_file, server_stop, port)
server_log_file, server_stop, port, server_type='minecraft-java')
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):
@ -394,9 +395,102 @@ class Controller:
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
server_log_file, server_stop, port)
server_log_file, server_stop, port, server_type='minecraft-java')
return new_id
#************************************************************************************************
# BEDROCK IMPORTS
#************************************************************************************************
def import_bedrock_server(self, server_name: str, server_path: str, server_exe: str, 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.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
server_path = helper.get_os_understandable_path(server_path)
dir_util.copy_tree(server_path, new_server_dir)
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == 'server.properties':
has_properties = True
if not has_properties:
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_exe)
#due to adding strings this must not be an fstring
if helper.is_os_windows():
server_command = f'"{full_jar_path}"'
else:
server_command = f'./{server_exe}'
logger.debug('command: ' + server_command)
server_log_file = "N/A"
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
server_log_file, server_stop, port, server_type='minecraft-bedrock')
os.chmod(full_jar_path, 2775)
return new_id
def import_bedrock_zip_server(self, server_name: str, zip_path: str, server_exe: str, 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.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
tempDir = helper.get_os_understandable_path(zip_path)
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
has_properties = False
#extracts archive to temp directory
for item in os.listdir(tempDir):
if str(item) == 'server.properties':
has_properties = True
try:
shutil.move(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
except Exception as ex:
logger.error(f'ERROR IN ZIP IMPORT: {ex}')
if not has_properties:
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_exe)
#due to strings being added we need to leave this as not an fstring
if helper.is_os_windows():
server_command = f'"{full_jar_path}"'
else:
server_command = f'./{server_exe}'
logger.debug('command: ' + server_command)
server_log_file = "N/A"
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
server_log_file, server_stop, port, server_type='minecraft-bedrock')
os.chmod(full_jar_path, 2775)
return new_id
#************************************************************************************************
# BEDROCK IMPORTS END
#************************************************************************************************
def rename_backup_dir(self, old_server_id, new_server_id, new_uuid):
server_data = self.servers.get_server_data_by_id(old_server_id)
old_bu_path = server_data['backup_path']
@ -421,11 +515,12 @@ class Controller:
server_file: str,
server_log_file: str,
server_stop: str,
server_port: int):
server_port: int,
server_type: str):
# put data in the db
new_id = self.servers.create_server(
name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_type, server_port)
if not helper.check_file_exists(os.path.join(server_dir, "crafty_managed.txt")):
try:

View File

@ -201,8 +201,8 @@ class Server:
logger.info(f"Launching Server {self.name} with command {self.server_command}")
console.info(f"Launching Server {self.name} with command {self.server_command}")
#Checks for eula. Creates one if none detected.
#If EULA is detected and not set to one of these true vaiants we offer to set it true.
#Checks for eula. Creates one if none detected.
#If EULA is detected and not set to one of these true vaiants we offer to set it true.
if helper.check_file_exists(os.path.join(self.settings['path'], 'eula.txt')):
f = open(os.path.join(self.settings['path'], 'eula.txt'), 'r', encoding='utf-8')
line = f.readline().lower()
@ -240,24 +240,40 @@ class Server:
logger.info(f"Starting server in {self.server_path} with command: {self.server_command}")
try:
self.process = subprocess.Popen(
self.server_command, cwd=self.server_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception as ex:
#Checks for java on initial fail
if os.system("java -version") == 32512:
if not helper.is_os_windows() and servers_helper.get_server_type_by_id(self.server_id) == "minecraft-bedrock":
logger.info(f"Bedrock and Unix detected for server {self.name}. Switching to appropriate execution string")
my_env = os.environ
my_env["LD_LIBRARY_PATH"] = self.server_path
try:
self.process = subprocess.Popen(
self.server_command, cwd=self.server_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=my_env)
except Exception as ex:
logger.error(f"Server {self.name} failed to start with error code: {ex}")
if user_id:
websocket_helper.broadcast_user(user_id, 'send_start_error',{
'error': translation.translate('error', 'noJava', user_lang).format(self.name)
})
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
})
return False
else:
try:
self.process = subprocess.Popen(
self.server_command, cwd=self.server_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception as ex:
#Checks for java on initial fail
if os.system("java -version") == 32512:
if user_id:
websocket_helper.broadcast_user(user_id, 'send_start_error',{
'error': translation.translate('error', 'noJava', user_lang).format(self.name)
})
return False
else:
logger.error(f"Server {self.name} failed to start with error code: {ex}")
if user_id:
websocket_helper.broadcast_user(user_id, 'send_start_error',{
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
})
return False
else:
logger.error(f"Server {self.name} failed to start with error code: {ex}")
if user_id:
websocket_helper.broadcast_user(user_id, 'send_start_error',{
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
})
return False
out_buf = ServerOutBuf(self.process, self.server_id)
@ -359,6 +375,7 @@ class Server:
server_name = self.name
server_pid = self.process.pid
while running:
x = x+1
logstr = f"Server {server_name} is still running - waiting 2s to see if it stops ({int(60-(x*2))} seconds until force close)"
@ -426,7 +443,6 @@ class Server:
def crash_detected(self, name):
print("crash detected")
# clear the old scheduled watcher task
self.server_scheduler.remove_job(f"c_{self.server_id}")
@ -477,6 +493,13 @@ class Server:
# if all is okay, we just exit out
if running:
return
#check the exit code -- This could be a fix for /stop
if self.process.returncode == 0:
logger.warning(f'Process {self.process.pid} exited with code {self.process.returncode}. This is considered a clean exit'+
' supressing crash handling.')
# cancel the watcher task
self.server_scheduler.remove_job("c_"+str(self.server_id))
return
servers_helper.sever_crashed(self.server_id)
# if we haven't tried to restart more 3 or more times

View File

@ -4,6 +4,7 @@ import time
import logging
import threading
import asyncio
import datetime
from tzlocal import get_localzone
from app.classes.shared.helpers import helper
@ -117,7 +118,6 @@ class TasksManager:
def _main_graceful_exit(self):
try:
os.remove(helper.session_file)
os.remove(os.path.join(helper.root_dir, '.header'))
self.controller.stop_all_servers()
except:
logger.info("Caught error during shutdown", exc_info=True)
@ -160,59 +160,60 @@ class TasksManager:
#load schedules from DB
for schedule in schedules:
if schedule.cron_string != "":
try:
self.scheduler.add_job(management_helper.add_command,
CronTrigger.from_crontab(schedule.cron_string,
timezone=str(self.tz)),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
except Exception as e:
console.error(f"Failed to schedule task with error: {e}.")
console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.")
logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(schedule.schedule_id)
else:
if schedule.interval_type == 'hours':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = 0,
hour = '*/'+str(schedule.interval),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
elif schedule.interval_type == 'minutes':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = '*/'+str(schedule.interval),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
elif schedule.interval_type == 'days':
curr_time = schedule.start_time.split(':')
self.scheduler.add_job(management_helper.add_command,
'cron',
day = '*/'+str(schedule.interval),
hour=curr_time[0],
minute=curr_time[1],
id=str(schedule.schedule_id),
args=[schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
if schedule.interval != 'reaction':
if schedule.cron_string != "":
try:
self.scheduler.add_job(management_helper.add_command,
CronTrigger.from_crontab(schedule.cron_string,
timezone=str(self.tz)),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
except Exception as e:
console.error(f"Failed to schedule task with error: {e}.")
console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.")
logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(schedule.schedule_id)
else:
if schedule.interval_type == 'hours':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = 0,
hour = '*/'+str(schedule.interval),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
elif schedule.interval_type == 'minutes':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = '*/'+str(schedule.interval),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
elif schedule.interval_type == 'days':
curr_time = schedule.start_time.split(':')
self.scheduler.add_job(management_helper.add_command,
'cron',
day = '*/'+str(schedule.interval),
hour=curr_time[0],
minute=curr_time[1],
id=str(schedule.schedule_id),
args=[schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
self.scheduler.start()
jobs = self.scheduler.get_jobs()
logger.info("Loaded schedules. Current enabled schedules: ")
@ -230,8 +231,14 @@ class TasksManager:
"None",
job_data['enabled'],
job_data['one_time'],
job_data['cron_string'])
if job_data['enabled']:
job_data['cron_string'],
job_data['parent'],
job_data['delay'])
#Checks to make sure some doofus didn't actually make the newly created task a child of itself.
if str(job_data['parent']) == str(sch_id):
management_helper.update_scheduled_task(sch_id, {'parent':None})
#Check to see if it's enabled and is not a chain reaction.
if job_data['enabled'] and job_data['interval_type'] != 'reaction':
if job_data['cron_string'] != "":
try:
self.scheduler.add_job(management_helper.add_command,
@ -293,12 +300,15 @@ class TasksManager:
def remove_all_server_tasks(self, server_id):
schedules = management_helper.get_schedules_by_server(server_id)
for schedule in schedules:
self.remove_job(schedule.schedule_id)
if schedule.interval != 'reaction':
self.remove_job(schedule.schedule_id)
def remove_job(self, sch_id):
job = management_helper.get_scheduled_task_model(sch_id)
for schedule in management_helper.get_child_schedules(sch_id):
management_helper.update_scheduled_task(schedule.schedule_id, {'parent':None})
management_helper.delete_scheduled_task(sch_id)
if job.enabled:
if job.enabled and job.interval_type != 'reaction':
self.scheduler.remove_job(str(sch_id))
logger.info(f"Job with ID {sch_id} was deleted.")
else:
@ -307,62 +317,67 @@ class TasksManager:
def update_job(self, sch_id, job_data):
management_helper.update_scheduled_task(sch_id, job_data)
#Checks to make sure some doofus didn't actually make the newly created task a child of itself.
if str(job_data['parent']) == str(sch_id):
management_helper.update_scheduled_task(sch_id, {'parent':None})
try:
self.scheduler.remove_job(str(sch_id))
if job_data['interval'] != 'reaction':
self.scheduler.remove_job(str(sch_id))
except:
logger.info("No job found in update job. Assuming it was previously disabled. Starting new job.")
if job_data['enabled']:
if job_data['cron_string'] != "":
try:
self.scheduler.add_job(management_helper.add_command,
CronTrigger.from_crontab(job_data['cron_string'],
timezone=str(self.tz)),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
except Exception as e:
console.error(f"Failed to schedule task with error: {e}.")
console.info("Removing failed task from DB.")
management_helper.delete_scheduled_task(sch_id)
else:
if job_data['interval_type'] == 'hours':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = 0,
hour = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'minutes':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'days':
curr_time = job_data['start_time'].split(':')
self.scheduler.add_job(management_helper.add_command,
'cron',
day = '*/'+str(job_data['interval']),
hour = curr_time[0],
minute = curr_time[1],
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
if job_data['interval'] != 'reaction':
if job_data['cron_string'] != "":
try:
self.scheduler.add_job(management_helper.add_command,
CronTrigger.from_crontab(job_data['cron_string'],
timezone=str(self.tz)),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
except Exception as e:
console.error(f"Failed to schedule task with error: {e}.")
console.info("Removing failed task from DB.")
management_helper.delete_scheduled_task(sch_id)
else:
if job_data['interval_type'] == 'hours':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = 0,
hour = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'minutes':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'days':
curr_time = job_data['start_time'].split(':')
self.scheduler.add_job(management_helper.add_command,
'cron',
day = '*/'+str(job_data['interval']),
hour = curr_time[0],
minute = curr_time[1],
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
else:
try:
self.scheduler.get_job(str(sch_id))
@ -376,9 +391,21 @@ class TasksManager:
task = management_helper.get_scheduled_task_model(int(event.job_id))
management_helper.add_to_audit_log_raw('system', users_helper.get_user_id_by_name('system'), task.server_id,
f"Task with id {task.schedule_id} completed successfully", '127.0.0.1')
#check if the task is a single run.
if task.one_time:
self.remove_job(task.schedule_id)
logger.info("one time task detected. Deleting...")
#check for any child tasks for this. It's kind of backward, but this makes DB management a lot easier. One to one instead of one to many.
for schedule in management_helper.get_child_schedules_by_server(task.schedule_id, task.server_id):
#event job ID's are strings so we need to look at this as the same data type.
if str(schedule.parent) == str(event.job_id):
if schedule.enabled:
delaytime = datetime.datetime.now() + datetime.timedelta(seconds=schedule.delay)
self.scheduler.add_job(management_helper.add_command, 'date', run_date=delaytime, id=str(schedule.schedule_id),
args=[schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command])
else:
logger.info("Event job ID is not numerical. Assuming it's stats - not stored in DB. Moving on.")
else:

View File

@ -258,6 +258,10 @@ class AjaxHandler(BaseHandler):
# Create the directory
os.mkdir(dir_path)
elif page == "send_order":
self.controller.users.update_server_order(exec_user['user_id'], bleach.clean(self.get_argument('order')))
return
elif page == "unzip_file":
if not permissions['Files'] in user_perms:
if not superuser:

View File

@ -307,6 +307,29 @@ class PanelHandler(BaseHandler):
list(filter(lambda x: x['stats']['running'], page_data['servers'])))
page_data['server_stats']['stopped'] = len(page_data['servers']) - page_data['server_stats']['running']
#set user server order
user_order = self.controller.users.get_user_by_id(exec_user['user_id'])
user_order = user_order['server_order'].split(',')
page_servers = []
server_ids = []
for server_id in user_order:
for server in page_data['servers']:
if str(server['server_data']['server_id']) == str(server_id):
page_servers.append(server)
for server in page_data['servers']:
server_ids.append(str(server['server_data']['server_id']))
if server not in page_servers:
page_servers.append(server)
for server_id in user_order:
#remove IDs in list that user no longer has access to
if str(server_id) not in server_ids:
user_order.remove(server_id)
page_data['servers'] = page_servers
for data in page_data['servers']:
data['stats']['crashed'] = self.controller.servers.is_crashed(
str(data['stats']['server_id']['server_id']))
@ -333,7 +356,7 @@ class PanelHandler(BaseHandler):
if server_id is None:
return
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls', 'tasks']
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls', 'schedules']
if subpage not in valid_subpages:
logger.debug('not a valid subpage')
@ -363,6 +386,7 @@ class PanelHandler(BaseHandler):
}
page_data['user_permissions'] = self.controller.server_perms.get_user_id_permissions_list(exec_user["user_id"], server_id)
page_data['server_stats']['crashed'] = self.controller.servers.is_crashed(server_id)
page_data['server_stats']['server_type'] = self.controller.servers.get_server_type_by_id(server_id)
if subpage == 'term':
if not page_data['permissions']['Terminal'] in page_data['user_permissions']:
@ -377,10 +401,10 @@ class PanelHandler(BaseHandler):
return
if subpage == 'tasks':
if subpage == 'schedules':
if not page_data['permissions']['Schedule'] in page_data['user_permissions']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
self.redirect("/panel/error?error=Unauthorized access To Schedules")
return
page_data['schedules'] = management_helper.get_schedules_by_server(server_id)
@ -559,8 +583,9 @@ class PanelHandler(BaseHandler):
elif page == "add_schedule":
server_id = self.get_argument('id', None)
page_data['schedules'] = management_helper.get_schedules_by_server(server_id)
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
page_data['active_link'] = 'tasks'
page_data['active_link'] = 'schedules'
page_data['permissions'] = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -574,8 +599,10 @@ class PanelHandler(BaseHandler):
page_data['user_permissions'] = self.controller.server_perms.get_user_id_permissions_list(exec_user["user_id"], server_id)
page_data['server_data'] = self.controller.servers.get_server_data_by_id(server_id)
page_data['server_stats'] = self.controller.servers.get_server_stats_by_id(server_id)
page_data['server_stats']['server_type'] = self.controller.servers.get_server_type_by_id(server_id)
page_data['new_schedule'] = True
page_data['schedule'] = {}
page_data['schedule']['children'] = []
page_data['schedule']['server_id'] = server_id
page_data['schedule']['schedule_id'] = ''
page_data['schedule']['action'] = ""
@ -591,17 +618,18 @@ class PanelHandler(BaseHandler):
if not Enum_Permissions_Server.Schedule in page_data['user_permissions']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
self.redirect("/panel/error?error=Unauthorized access To Schedules")
return
template = "panel/server_schedule_edit.html"
elif page == "edit_schedule":
server_id = self.get_argument('id', None)
page_data['schedules'] = management_helper.get_schedules_by_server(server_id)
sch_id = self.get_argument('sch_id', None)
schedule = self.controller.management.get_scheduled_task_model(sch_id)
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
page_data['active_link'] = 'tasks'
page_data['active_link'] = 'schedules'
page_data['permissions'] = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -615,11 +643,13 @@ class PanelHandler(BaseHandler):
page_data['user_permissions'] = self.controller.server_perms.get_user_id_permissions_list(exec_user["user_id"], server_id)
page_data['server_data'] = self.controller.servers.get_server_data_by_id(server_id)
page_data['server_stats'] = self.controller.servers.get_server_stats_by_id(server_id)
page_data['server_stats']['server_type'] = self.controller.servers.get_server_type_by_id(server_id)
page_data['new_schedule'] = False
page_data['schedule'] = {}
page_data['schedule']['server_id'] = server_id
page_data['schedule']['schedule_id'] = schedule.schedule_id
page_data['schedule']['action'] = schedule.action
page_data['schedule']['children'] = self.controller.management.get_child_schedules(sch_id)
# We check here to see if the command is any of the default ones.
# We do not want a user changing to a custom command and seeing our command there.
if schedule.action != 'start' or schedule.action != 'stop' or schedule.action != 'restart' or schedule.action != 'backup':
@ -632,7 +662,9 @@ class PanelHandler(BaseHandler):
page_data['schedule']['time'] = schedule.start_time
page_data['schedule']['interval'] = schedule.interval
page_data['schedule']['interval_type'] = schedule.interval_type
if schedule.cron_string == '':
if schedule.interval_type == 'reaction':
difficulty = 'reaction'
elif schedule.cron_string == '':
difficulty = 'basic'
else:
difficulty = 'advanced'
@ -643,7 +675,7 @@ class PanelHandler(BaseHandler):
if not Enum_Permissions_Server.Schedule in page_data['user_permissions']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
self.redirect("/panel/error?error=Unauthorized access To Schedules")
return
template = "panel/server_schedule_edit.html"
@ -1052,6 +1084,23 @@ class PanelHandler(BaseHandler):
command = "restart_server"
elif action == "backup":
command = "backup_server"
elif difficulty == 'reaction':
interval_type = 'reaction'
action = bleach.clean(self.get_argument('action', None))
delay = bleach.clean(self.get_argument('delay', None))
parent = bleach.clean(self.get_argument('parent', None))
if action == "command":
command = bleach.clean(self.get_argument('command', None))
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
else:
interval_type = ''
cron_string = bleach.clean(self.get_argument('cron', ''))
@ -1105,6 +1154,21 @@ class PanelHandler(BaseHandler):
"one_time": one_time,
"cron_string": ''
}
elif difficulty == "reaction":
job_data = {
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": '',
#We'll base every interval off of a midnight start time.
"start_time": '',
"command": command,
"cron_string": '',
"enabled": enabled,
"one_time": one_time,
"parent": parent,
"delay": delay
}
elif difficulty == "advanced":
job_data = {
"server_id": server_id,
@ -1116,7 +1180,9 @@ class PanelHandler(BaseHandler):
"command": command,
"cron_string": cron_string,
"enabled": enabled,
"one_time": one_time
"one_time": one_time,
"parent": None,
"delay": 0
}
else:
job_data = {
@ -1129,7 +1195,9 @@ class PanelHandler(BaseHandler):
#We'll base every interval off of a midnight start time.
"start_time": '00:00',
"one_time": one_time,
"cron_string": ''
"cron_string": '',
'parent': None,
'delay': 0
}
self.tasks_manager.schedule_job(job_data)
@ -1139,7 +1207,7 @@ class PanelHandler(BaseHandler):
server_id,
self.get_remote_ip())
self.tasks_manager.reload_schedule_from_db()
self.redirect(f"/panel/server_detail?id={server_id}&subpage=tasks")
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
if page == "edit_schedule":
@ -1164,6 +1232,22 @@ class PanelHandler(BaseHandler):
command = "restart_server"
elif action == "backup":
command = "backup_server"
elif difficulty == 'reaction':
interval_type = 'reaction'
action = bleach.clean(self.get_argument('action', None))
delay = bleach.clean(self.get_argument('delay', None))
parent = bleach.clean(self.get_argument('parent', None))
if action == "command":
command = bleach.clean(self.get_argument('command', None))
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
parent = bleach.clean(self.get_argument('parent', None))
else:
interval_type = ''
cron_string = bleach.clean(self.get_argument('cron', ''))
@ -1228,9 +1312,26 @@ class PanelHandler(BaseHandler):
"start_time": '',
"command": command,
"cron_string": cron_string,
"delay": '',
"parent": '',
"enabled": enabled,
"one_time": one_time
}
elif difficulty == "reaction":
job_data = {
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": '',
#We'll base every interval off of a midnight start time.
"start_time": '',
"command": command,
"cron_string": '',
"enabled": enabled,
"one_time": one_time,
"parent": parent,
"delay": delay
}
else:
job_data = {
"server_id": server_id,
@ -1241,6 +1342,8 @@ class PanelHandler(BaseHandler):
"enabled": enabled,
#We'll base every interval off of a midnight start time.
"start_time": '00:00',
"delay": '',
"parent": '',
"one_time": one_time,
"cron_string": ''
}
@ -1252,7 +1355,7 @@ class PanelHandler(BaseHandler):
server_id,
self.get_remote_ip())
self.tasks_manager.reload_schedule_from_db()
self.redirect(f"/panel/server_detail?id={server_id}&subpage=tasks")
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
elif page == "edit_user":

View File

@ -113,6 +113,13 @@ class ServerHandler(BaseHandler):
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data())
template = "server/wizard.html"
if page == "bedrock_step1":
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
return
template = "server/bedrock_wizard.html"
self.render(
template,
data=page_data,
@ -170,6 +177,7 @@ class ServerHandler(BaseHandler):
new_executable = server_data.get('executable')
new_server_log_file = str(helper.get_os_understandable_path(server_data.get('log_path'))).replace(server_uuid, new_server_uuid)
server_port = server_data.get('server_port')
server_type = server_data.get('server_type')
self.controller.servers.create_server(new_server_name,
new_server_uuid,
@ -179,6 +187,7 @@ class ServerHandler(BaseHandler):
new_executable,
new_server_log_file,
stop_command,
server_type,
server_port)
self.controller.init_all_servers()
@ -273,6 +282,89 @@ class ServerHandler(BaseHandler):
self.controller.stats.record_stats()
self.redirect("/panel/dashboard")
if page == "bedrock_step1":
if not superuser:
user_roles = self.controller.roles.get_all_roles()
else:
user_roles = self.controller.roles.get_all_roles()
server = bleach.clean(self.get_argument('server', ''))
server_name = bleach.clean(self.get_argument('server_name', ''))
port = bleach.clean(self.get_argument('port', ''))
import_type = bleach.clean(self.get_argument('create_type', ''))
import_server_path = bleach.clean(self.get_argument('server_path', ''))
import_server_exe = bleach.clean(self.get_argument('server_jar', ''))
server_parts = server.split("|")
captured_roles = []
for role in user_roles:
if bleach.clean(self.get_argument(str(role), '')) == "on":
captured_roles.append(role)
if not server_name:
self.redirect("/panel/error?error=Server name cannot be empty!")
return
if import_type == 'import_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
new_server_id = self.controller.import_bedrock_server(server_name, import_server_path,import_server_exe, port)
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"imported a jar server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
elif import_type == 'import_zip':
# here import_server_path means the zip path
zip_path = bleach.clean(self.get_argument('root_path'))
good_path = helper.check_path_exists(zip_path)
if not good_path:
self.redirect("/panel/error?error=Temp path not found!")
return
new_server_id = self.controller.import_bedrock_zip_server(server_name, zip_path, import_server_exe, port)
if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with" +
f"sudo chown -R crafty:crafty {import_server_path} And sudo chmod 2775 -R {import_server_path}")
return
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"imported a zip server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
#deletes temp dir
shutil.rmtree(zip_path)
else:
if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data")
return
server_type, server_version = server_parts
# TODO: add server type check here and call the correct server add functions if not a jar
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"])
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"created a {server_version} {str(server_type).capitalize()} server named \"{server_name}\"",
# Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip())
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
if len(captured_roles) == 0:
if not superuser:
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
role_id = self.controller.roles.add_role(f"Creator of Server with uuid={new_server_uuid}")
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.users.add_role_to_user(exec_user["user_id"], role_id)
self.controller.crafty_perms.add_server_creation(exec_user["user_id"])
else:
for role in captured_roles:
role_id = role
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.stats.record_stats()
self.redirect("/panel/dashboard")
try:
self.render(
template,

View File

@ -13,6 +13,7 @@
"show_contribute_link": true,
"virtual_terminal_lines": 70,
"max_log_lines": 700,
"max_audit_entries": 300,
"keywords": ["help", "chunk"],
"allow_nsfw_profile_pictures": false
}

View File

@ -123,10 +123,10 @@
</thead>
<tbody>
{% for server in data['servers'] %}
<tr>
<td>
<tr id="{{server['server_data']['server_id']}}" draggable="true" ondragstart="start()" ondragover="dragover()" ondragend="dragend()">
<td draggable="false">
<i class="fas fa-server"></i>
<a href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<a draggable="false" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
{{ server['server_data']['server_name'] }}
</a>
</td>
@ -391,7 +391,7 @@
server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
server_infos = "";
server_infos = server.online + " / " + server.max + "{{ translate('dashboard', 'max', data['lang']) }}<br />"
server_infos = server.online + " / " + server.max + " {{ translate('dashboard', 'max', data['lang']) }}<br />"
}
/* Update Motd */
@ -554,5 +554,55 @@
});
</script>
<script>
var row;
function start(){
row = event.target;
}
function dragover(){
var e = event;
e.preventDefault();
let children= Array.from(e.target.parentNode.parentNode.children);
if(children.indexOf(e.target.parentNode)>children.indexOf(row))
e.target.parentNode.after(row);
else
e.target.parentNode.before(row);
}
function dragend(){
var id_string = '';
const table = document.querySelector("table");
for (const row of table.rows) {
if (row.getAttribute('id') != null){
if (id_string != ''){
id_string += ',' + String(row.getAttribute('id'));
}else{
id_string +=String(row.getAttribute('id'));
}
}
}
console.log(id_string)
sendOrder(id_string)
}
function sendOrder(id_string) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/send_order?order='+id_string,
data: {
order: id_string,
},
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
</script>
{% end %}

View File

@ -133,7 +133,7 @@
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-lock"></i> {{ translate('userConfig', 'craftyPerms', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'craftyPermDesc', data['lang']) }}permissions this user has on Crafty Controller </small></h4>
<h4 class="card-title"><i class="fas fa-user-lock"></i> {{ translate('userConfig', 'craftyPerms', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'craftyPermDesc', data['lang']) }}</small></h4>
</div>
<div class="card-body">
<div class="form-group">

View File

@ -6,7 +6,7 @@
<div class="col-sm-4 mr-2">
{% if data['server_stats']['running'] %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }} ({{ translate('serverStats', 'serverTime', data['lang']) }})</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }}</span><br />
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }}</span>
{% elif data['server_stats']['crashed'] %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-danger"> <i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}</span><br />
@ -16,7 +16,6 @@
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span>
<p>{{ data['server_stats']['crashed'] }}</p>
{% end %}
<br>
<b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{ data['serverTZ'] }}</span>
@ -40,6 +39,8 @@
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> <span id="version">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
{% end %}
<b>Server Type: <span class="text-info">{{data['server_stats']['server_type']}}</span></b>
</div>
</div>
</div>
@ -134,17 +135,15 @@
server_input_motd = document.getElementById('input_motd');
/* TODO Update each element */
if (server.running)
{
if (server.int_ping_results)
{
server_status.setAttribute("class", "text-success");
server_status.innerHTML = `{{ translate('serverStats', 'online', data['lang']) }}`;
if (server.running){
if (server['stats']['waiting_start']){
server_status.setAttribute("class", "text-warning");
server_status.innerHTML = `{{ translate('serverStats', 'starting', data['lang']) }}`;
}
else
{
server_status.setAttribute("class", "text-warning");
server_status.innerHTML = `{{ translate('serverStats', 'starting', data['lang']) }}`;
server_status.setAttribute("class", "text-success");
server_status.innerHTML = `{{ translate('serverStats', 'online', data['lang']) }}`;
}
startedUTC = server.started;
@ -153,7 +152,7 @@
startedLocal = startedUTC.utcOffset(browserUTCOffset);
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
server_started.setAttribute("class", "");
server_started.innerHTML = startedLocalFormatted +` ({{ translate('serverStats', 'serverTime', data['lang']) }})`;
server_started.innerHTML = startedLocalFormatted;
server_uptime.setAttribute("class", "");
if (!uptimeLoop) {
var calculateUptime = () => {

View File

@ -5,7 +5,8 @@
<i class="fas fa-file-signature"></i>{{ translate('serverDetails', 'terminal', data['lang']) }}</a>
</li>
{% end %}
{% if data['permissions']['Logs'] in data['user_permissions'] %}
<!--Bedrock servers don't have logs so we'll only show it if we know it's not a bedrock server.-->
{% if data['permissions']['Logs'] in data['user_permissions'] and data['server_data']['type'] != 'minecraft-bedrock'%}
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'logs' %}active{% end %}" 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>{{ translate('serverDetails', 'logs', data['lang']) }}</a>
@ -13,7 +14,7 @@
{% end %}
{% if data['permissions']['Schedule'] in data['user_permissions'] %}
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'tasks' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
<a class="nav-link {% if data['active_link'] == 'schedules' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules" role="tab" aria-selected="false">
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule', data['lang']) }}</a>
</li>
{% end %}

View File

@ -24,7 +24,7 @@
</div>
<!-- Page Title Header Ends-->
{% include "parts/details_stats.html %}
{% include "parts/details_stats.html" %}
<div class="row">
@ -34,7 +34,7 @@
{% include "parts/server_controls_list.html %}
<div class="row">
<div class="col-md-12 col-sm-12">
<div class="col-md-8 col-sm-8">
{% if data['new_schedule'] == True %}
<form class="forms-sample" method="post" action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
{% else %}
@ -45,70 +45,99 @@
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="difficulty">Basic / Cron Select<small class="text-muted ml-1"></small> </label><br>
<label for="difficulty">Basic / Cron / Chain-Reaction Select<small class="text-muted ml-1"></small> </label><br>
<select id="difficulty" name="difficulty" onchange="basicAdvanced(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['difficulty'] }}">
<option id="basic" value="basic">Basic</option>
<option id="advanced" value="advanced">Advanced</option>
<option id="basic" value="basic">{{ translate('serverScheduleConfig', 'basic' , data['lang']) }}</option>
<option id="advanced" value="advanced">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }}</option>
<option id="reaction" value="reaction">{{ translate('serverScheduleConfig', 'reaction' , data['lang']) }}</option>
</select>
</div>
<div class="form-group">
<label for="server_name">Action<small class="text-muted ml-1"></small> </label><br>
<select id="action" name="action" onchange="yesnoCheck(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
<option id="start" value="start">Start Server</option>
<option id="restart" value="restart">Restart Server</option>
<option id="stop" value="stop">Shutdown Server</option>
<option id="backup" value="backup">Backup Server</option>
<option id="command" value="command">Custon Command</option>
<option id="start" value="start">{{ translate('serverScheduleConfig', 'start' , data['lang']) }}</option>
<option id="restart" value="restart">{{ translate('serverScheduleConfig', 'restart' , data['lang']) }}</option>
<option id="stop" value="stop">{{ translate('serverScheduleConfig', 'stop' , data['lang']) }}</option>
<option id="backup" value="backup">{{ translate('serverScheduleConfig', 'backup' , data['lang']) }}</option>
<option id="command" value="command">{{ translate('serverScheduleConfig', 'custom' , data['lang']) }}</option>
</select>
</div>
<div id="ifBasic">
<div class="form-group">
<label for="server_path">Interval <small class="text-muted ml-1"> - How often you want this task to execute</small> </label>
<label for="server_path">{{ translate('serverScheduleConfig', 'interval' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' , data['lang']) }}</small> </label>
<input type="number" class="form-control" name="interval" id="interval" value="{{ data['schedule']['interval'] }}" placeholder="Interval" required>
<br>
<br>
<select id="interval_type" onchange="ifDays(this);" name="interval_type" class="form-control form-control-lg select-css" value="{{ data['schedule']['interval_type'] }}">
<option id = "days" value="days">Days</option>
<option id = "hours" value="hours">Hours</option>
<option id = "minutes" value="minutes">Minutes</option>
<option id = "days" value="days">{{ translate('serverScheduleConfig', 'days' , data['lang']) }}</option>
<option id = "hours" value="hours">{{ translate('serverScheduleConfig', 'hours' , data['lang']) }}</option>
<option id = "minutes" value="minutes">{{ translate('serverScheduleConfig', 'minutes' , data['lang']) }}</option>
</select>
</div>
<div id="ifDays" style="display: block;">
<div class="form-group">
<label for="time">Time <small class="text-muted ml-1"> - What time do you want your task to execute?</small> </label>
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' , data['lang']) }}</small> </label>
<input type="time" class="form-control" name="time" id="time" value="{{ data['schedule']['time'] }}" placeholder="Time" required>
</div>
</div>
</div>
<div id="ifYes" style="display: none;">
<div class="form-group">
<label for="command">Command <small class="text-muted ml-1"> - What command do you want us to execute? Do not include the '/'</small> </label>
<label for="command">{{ translate('serverScheduleConfig', 'command' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'command-explain' , data['lang']) }}</small> </label>
<input type="input" class="form-control" name="command" id="command_input" value="{{ data['schedule']['command'] }}" placeholder="Command" required>
</div>
</div>
<div id="ifAdvanced" style="display: none;">
<div class="form-group">
<label for="cron">Cron <small class="text-muted ml-1"> - Input your cron string</small> </label>
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang']) }}</small> </label>
<input type="input" class="form-control" name="cron" id="cron" value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
</div>
</div>
<div id="ifReaction" style="display: none;">
<div class="form-group">
<label for="delay">{{ translate('serverScheduleConfig', 'offset' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'offset-explain' , data['lang']) }}</small> </label>
<input type="number" class="form-control" name="delay" id="delay" value="0">
<br>
<br>
<label for="parent">{{ translate('serverScheduleConfig', 'parent' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'parent-explain' , data['lang']) }}</small> </label>
<select id="parent" name="parent" class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
{% for schedule in data['schedules'] %}
{% if schedule.schedule_id != data['schedule']['schedule_id'] %}
{% if schedule.interval != '' %}
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">ID: {{schedule.schedule_id}} | {{schedule.command}} | {{schedule.interval}} {{ schedule.interval_type}}</option>
{% else %}
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">ID: {{schedule.schedule_id}} {{schedule.command}} {{schedule.cron_string}}</option>
{% end %}
{% end %}
{% end %}
</select>
</div>
</div>
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">Enabled
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">{{ translate('serverScheduleConfig', 'enabled' , data['lang']) }}
</label>
</div>
<div class="form-check-flat">
<label for="one_time" class="form-check-label ml-4 mb-4">
<input type="checkbox" class="form-check-input" id="one_time" name="one_time" value="1">Delete After Execution
<input type="checkbox" class="form-check-input" id="one_time" name="one_time" value="1">{{ translate('serverScheduleConfig', 'one-time' , data['lang']) }}
</label>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('serverConfig', 'save', data['lang']) }}</button>
<button type="reset" onclick="location.href=`/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks`" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
<button type="reset" onclick="location.href=`/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules`" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
</form>
</div>
<div class="col-sm-4 grid-margin">
<h4>{{ translate('serverScheduleConfig', 'children' , data['lang']) }}</h4>
<ul>
{% for schedule in data['schedule']['children'] %}
<li style="overflow: auto;"><a href="/panel/edit_schedule?id={{schedule.server_id}}&sch_id={{schedule.schedule_id}}">{{schedule.schedule_id}} | {{schedule.command}}</a></li>
{% end %}
</ul>
</div>
</div>
</div>
@ -149,12 +178,27 @@
function basicAdvanced() {
if (document.getElementById('difficulty').value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifReaction").style.display = "none";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("delay").required = false;
document.getElementById("parent").required = false;
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
} else {
} else if(document.getElementById('difficulty').value == "reaction"){
document.getElementById("ifReaction").style.display = "block";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("delay").required = true;
document.getElementById("parent").required = true;
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
}
else {
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifReaction").style.display = "none";
document.getElementById("ifBasic").style.display = "block";
document.getElementById("delay").required = false;
document.getElementById("parent").required = false;
document.getElementById("interval").required = true;
document.getElementById("time").required = true;
}

View File

@ -45,7 +45,8 @@
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" style="table-layout:fixed;">
<thead>
<tr class="rounded">
<th style="width: 25%; min-width: 50px;">Action</th>
<th style="width: 2%; min-width: 10px;">ID</th>
<th style="width: 23%; min-width: 50px;">Action</th>
<th style="width: 40%; min-width: 50px;">Command</th>
<th style="width: 10%; min-width: 50px;">Interval</th>
<th style="width: 10%; min-width: 50px;">Start Time</th>
@ -56,6 +57,9 @@
<tbody>
{% for schedule in data['schedules'] %}
<tr>
<td id="{{schedule.schedule_id}}" class="id">
<p>{{schedule.schedule_id}}</p>
</td>
<td id="{{schedule.action}}" class="action">
<p>{{schedule.action}}</p>
</td>
@ -66,6 +70,8 @@
{% if schedule.interval != '' %}
<p>Every</p>
<p>{{schedule.interval}} {{schedule.interval_type}}</p>
{% elif schedule.interval_type == 'reaction' %}
<p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p>
{% else %}
<p>Cron String:</p>
<p>{{schedule.cron_string}}</p>
@ -141,6 +147,9 @@
</div>
<div class="modal-body">
<ul style="list-style: none;">
<li id="{{schedule.schedule_id}}" class="id" style="border-top: .1em solid gray;">
<h4>ID</h4><p>{{schedule.schedule_id}}</p>
</li>
<li id="{{schedule.action}}" class="action" style="border-top: .1em solid gray;">
<h4>Action</h4><p>{{schedule.action}}</p>
</li>
@ -150,6 +159,8 @@
<li id="{{schedule.interval}}" class="action" style="border-top: .1em solid gray;">
{% if schedule.interval != '' %}
<h4>Interval</h4> <p>Every {{schedule.interval}} {{schedule.interval_type}}</p>
{% elif schedule.interval_type == 'reaction' %}
<h4>Interval</h4> <p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p>
{% else %}
<h4>Interval</h4> <p>Cron String: {{schedule.cron_string}}</p>
{% end %}

View File

@ -0,0 +1,460 @@
{% extends ../base.html %}
{% block title %}Crafty Controller - {{ translate('serverWizard', 'newServer', data['lang']) }}{% end %}
{% block content %}
<div class="content-wrapper">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item term-nav-item">
<a class="nav-link" href="/server/step1" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Minecraft-Java</a>
</li>
<li class="nav-item term-nav-item">
<a class="nav-link active" href="/server/bedrock_step1" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Minecraft-Bedrock</a>
</li>
</ul>
<br>
<div class="d-none" id="overlay" onclick="hide(event)"></div>
<div class="row">
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
<br />
<p class="card-description">
<form method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_jar" name="create_type">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
</div>
</div>
</div>
<br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
<hr>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small></small></label>
<input type="number" class="form-control" id="port2" name="port" value="19132" step="1" min="1" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<div id="accordion-2">
<div class="card">
<div class="card-header p-2" id="Role-2">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
</p>
</div>
<div id="collapseRole-2" class="collapse" aria-labelledby="Role-2" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
</form>
</p>
</div>
</div>
</div>
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
<br />
<p class="card-description">
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
<div class="row">
<div class="col-sm-9">
<div class="col-sm-12">
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{ translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_files_button" type="button">{{ translate('serverWizard', 'clickRoot', data['lang']) }}</button>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
</div>
</div>
</div>
</div>
<div class="col-sm-12">
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
<hr>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small></small></label>
<input type="number" class="form-control" id="port3" name="port" value="19132" step="1" min="1" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<div id="accordion-3">
<div class="card">
<div class="card-header p-2" id="Role-3">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
</p>
</div>
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-12" style="visibility: hidden;">
<div class="form-group">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
</div>
</div>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard', 'selectZipDir', data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input" name="root_path" value="" checked>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{{ translate('serverFiles', 'files', data['lang']) }}
</span>
</input>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverWizard', 'close', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button>
</div>
</div>
</div>
</div>
</div>
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
</div>
</div>
</form>
</p>
</div>
</div>
</div>
</div>
<style>
.scroll {
max-height: 12em;
overflow-y: auto;
}
.menu-btn {
font-size: 0.9em;
padding: 2px 10px;
}
.menu {
padding-top: 10px;
z-index: 200;
margin-top: 4px;
position: absolute;
background-color: #2a2c44;
}
.menu-option {
padding: 6px 20px 6px;
color: white;
}
#overlay {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 100;
}
</style>
<style>
/* Remove default bullets */
.tree-view,
.tree-nested {
list-style-type: none;
margin: 0;
padding: 0;
margin-left: 10px;
}
/* Style the items */
.tree-item,
.files-tree-title {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */
.tree-caret .fa-folder {
display: inline-block;
}
.tree-caret .fa-folder-open {
display: none;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.tree-caret-down .fa-folder {
display: none;
}
.tree-caret-down .fa-folder-open {
display: inline-block;
}
/* Hide the nested list */
.tree-nested {
display: none;
}
</style>
{% end %}
{% block js%}
<script>
document.getElementById("root_files_button").addEventListener("click", function(){
if(document.forms["zip"]["server_path"].value != ""){
if(document.getElementById('root_files_button').classList.contains('clicked')){
document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input" name="root_path" value="" checked><span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate('serverFiles', 'files', data['lang']) }}</span></input>'
}else{
document.getElementById('root_files_button').classList.add('clicked')
}
path = document.forms["zip"]["server_path"].value;
console.log(document.forms["zip"]["server_path"].value)
var token = getCookie("_xsrf");
var dialog = bootbox.dialog({
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
closeButton: false
});
setTimeout(function(){
dialog.modal('hide');
}, 5000);
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/unzip_server?id=-1&path='+path,
});
}else{
bootbox.alert("You must input a path before selecting this button");
}
});
</script>
<script>
function dropDown(event) {
event.target.parentElement.children[1].classList.remove("d-none");
document.getElementById("overlay").classList.remove("d-none");
}
function hide(event) {
var items = document.getElementsByClassName('menu');
for (let i = 0; i < items.length; i++) {
items[i].classList.add("d-none");
}
document.getElementById("overlay").classList.add("d-none");
}
function wait_msg(importing){
bootbox.alert({
title: importing ? '{% raw translate("serverWizard", "importing", data['lang']) %}' : '{% raw translate("serverWizard", "downloading", data['lang']) %}',
message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data['lang']) %}',
});
}
function show_file_tree(){
$("#dir_select").modal();
}
function getTreeView(path) {
document.getElementById('zip_submit').disabled = false;
path = path
$.ajax({
type: "GET",
url: '/ajax/get_zip_tree?id=-1&path='+path,
dataType: 'text',
success: function(data){
console.log("got response:");
console.log(data);
dataArr = data.split('\n');
serverDir = dataArr.shift(); // Remove & return first element (server directory)
text = dataArr.join('\n');
try{
document.getElementById('main-tree-div').innerHTML += text;
document.getElementById('main-tree').parentElement.classList.add("clicked");
}catch{
document.getElementById('files-tree').innerHTML = text;
}
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
},
});
}
function getToggleMain(event) {
path = event.target.parentElement.getAttribute('data-path');
document.getElementById("files-tree").classList.toggle("d-block");
document.getElementById(path+"span").classList.toggle("tree-caret-down");
document.getElementById(path+"span").classList.toggle("tree-caret");
}
function getDirView(event) {
path = event.target.parentElement.getAttribute('data-path');
if (document.getElementById(path).classList.contains('clicked')){
var toggler = document.getElementById(path+"span");
if (toggler.classList.contains('files-tree-title')){
document.getElementById(path+"ul").classList.toggle("d-block");
document.getElementById(path+"span").classList.toggle("tree-caret-down");
}
return;
}else{
$.ajax({
type: "GET",
url: '/ajax/get_zip_dir?id=-1&path='+path,
dataType: 'text',
success: function(data){
console.log("got response:");
dataArr = data.split('\n');
serverDir = dataArr.shift(); // Remove & return first element (server directory)
text = dataArr.join('\n');
try{
document.getElementById(path+"span").classList.add('tree-caret-down');
document.getElementById(path).innerHTML += text;
document.getElementById(path).classList.add("clicked");
}catch{
console.log("Bad")
}
var toggler = document.getElementById(path);
if (toggler.classList.contains('files-tree-title')){
document.getElementById(path+"span").addEventListener("click", function caretListener() {
document.getElementById(path+"ul").classList.toggle("d-block");
document.getElementById(path+"span").classList.toggle("tree-caret-down");
});
}
},
});
}
}
if (webSocket) {
webSocket.on('send_temp_path', function (data) {
document.getElementById('main-tree-input').setAttribute('value', data.path)
getTreeView(data.path);
show_file_tree();
});
}
</script>
<script type="text/javascript">
//<![CDATA[
// array of possible countries in the same order as they appear in the country selection list
function decodeHtmlCharCodes(str) {
return str.replace("&quot;", "\"");
}
function convertHtmlJsonToJavacriptArray(str) {
var result = []
str = decodeHtmlCharCodes(str)
for(var i in str)
result.push([i, str [i]]);
return result
}
//]]>
</script>
{% end %}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('schedules', parent=peewee.IntegerField(null=True))
migrator.add_columns('schedules', delay=peewee.IntegerField(default=0))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('schedules', ['parent'])
migrator.drop_columns('schedules', ['delay'])
"""
Write your rollback migrations here.
"""

View File

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

View File

@ -49,7 +49,7 @@
"serverVersion": "Server Version",
"selectVersion": "Select a Version",
"absoluteServerPath": "Absolute path to your server",
"serverJar": "Server Jarfile",
"serverJar": "Server Executable File",
"minMem": "Minimum Memory",
"maxMem": "Maximum Memory",
"serverPort": "Server Port",
@ -178,6 +178,34 @@
"cannotSee": "Not seeing everything?",
"cannotSeeOnMobile": "Try clicking on a scheduled task for full details."
},
"serverScheduleConfig":{
"basic": "Basic",
"cron": "Cron",
"reaction": "Reaction",
"start": "Start Server",
"restart": "Restart Server",
"stop": "Shutdown Server",
"backup": "Backup Server",
"custom": "Custom Command",
"interval": "Interval",
"interval-explain": "How often do you want this schedule executed?",
"days": "Days",
"hours": "Hours",
"minutes": "Minutes",
"time": "Time",
"time-explain": "What time do you want your schedule to execute?",
"command": "Command",
"command-explain": "What command do you want us to execute? Do not include the '/'",
"cron-explain": "Enter your cron string",
"offset": "Delay Offset",
"offset-explain": "How long should we wait to fire this after firing the first task? (Seconds)",
"parent": "Select a parent schedule",
"parent-explain": "Which schedule should trigger this one?",
"enabled": "Enabled",
"one-time": "Delete after execution",
"children": "Linked Child Tasks: "
},
"notify":{
"supportLogs": "Support Logs",
"activityLog": "Activity Logs",

View File

@ -15,3 +15,4 @@ services:
- ./docker/logs:/commander/logs
- ./docker/servers:/commander/servers
- ./docker/config:/commander/app/config
- ./import:/commander/import

View File

@ -15,3 +15,4 @@ services:
- ./logs:/commander/logs
- ./servers:/commander/servers
- ./config:/commander/app/config
- ./import:/commander/import