Merge branch 'dew-schedule' into 'dev'

First major Schedules Commit to Dev

See merge request crafty-controller/crafty-commander!129
This commit is contained in:
Andrew 2022-01-11 22:49:09 +00:00
commit 85bcac7af4
11 changed files with 819 additions and 120 deletions

View File

@ -91,6 +91,10 @@ class Management_Controller:
def get_scheduled_task(schedule_id): def get_scheduled_task(schedule_id):
return management_helper.get_scheduled_task(schedule_id) return management_helper.get_scheduled_task(schedule_id)
@staticmethod
def get_scheduled_task_model(schedule_id):
return management_helper.get_scheduled_task_model(schedule_id)
@staticmethod @staticmethod
def get_schedules_by_server(server_id): def get_schedules_by_server(server_id):
return management_helper.get_schedules_by_server(server_id) return management_helper.get_schedules_by_server(server_id)
@ -111,5 +115,5 @@ class Management_Controller:
return management_helper.get_backup_config(server_id) return management_helper.get_backup_config(server_id)
@staticmethod @staticmethod
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True): def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None):
return management_helper.set_backup_config(server_id, backup_path, max_backups, auto_enabled) return management_helper.set_backup_config(server_id, backup_path, max_backups)

View File

@ -112,6 +112,8 @@ class Schedules(Model):
start_time = CharField(null=True) start_time = CharField(null=True)
command = CharField(null=True) command = CharField(null=True)
comment = CharField() comment = CharField()
one_time = BooleanField(default=False)
cron_string = CharField(default="")
class Meta: class Meta:
table_name = 'schedules' table_name = 'schedules'
@ -205,7 +207,7 @@ class helpers_management:
# Schedules Methods # Schedules Methods
#************************************************************************************************ #************************************************************************************************
@staticmethod @staticmethod
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True): def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True, one_time=False, cron_string='* * * * *'):
sch_id = Schedules.insert({ sch_id = Schedules.insert({
Schedules.server_id: server_id, Schedules.server_id: server_id,
Schedules.action: action, Schedules.action: action,
@ -214,7 +216,10 @@ class helpers_management:
Schedules.interval_type: interval_type, Schedules.interval_type: interval_type,
Schedules.start_time: start_time, Schedules.start_time: start_time,
Schedules.command: command, Schedules.command: command,
Schedules.comment: comment Schedules.comment: comment,
Schedules.one_time: one_time,
Schedules.cron_string: cron_string
}).execute() }).execute()
return sch_id return sch_id
@ -235,6 +240,10 @@ class helpers_management:
def get_scheduled_task(schedule_id): def get_scheduled_task(schedule_id):
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute() return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute()
@staticmethod
def get_scheduled_task_model(schedule_id):
return Schedules.select().where(Schedules.schedule_id == schedule_id).get()
@staticmethod @staticmethod
def get_schedules_by_server(server_id): def get_schedules_by_server(server_id):
return Schedules.select().where(Schedules.server_id == server_id).execute() return Schedules.select().where(Schedules.server_id == server_id).execute()
@ -272,7 +281,7 @@ class helpers_management:
return conf return conf
@staticmethod @staticmethod
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True): def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None):
logger.debug("Updating server {} backup config with {}".format(server_id, locals())) logger.debug("Updating server {} backup config with {}".format(server_id, locals()))
try: try:
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0] row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
@ -297,7 +306,6 @@ class helpers_management:
new_row = True new_row = True
if max_backups is not None: if max_backups is not None:
conf['max_backups'] = max_backups conf['max_backups'] = max_backups
schd['enabled'] = bool(auto_enabled)
if not new_row: if not new_row:
with database.atomic(): with database.atomic():
if backup_path is not None: if backup_path is not None:
@ -305,15 +313,12 @@ class helpers_management:
else: else:
u1 = 0 u1 = 0
u2 = Backups.update(conf).where(Backups.server_id == server_id).execute() u2 = Backups.update(conf).where(Backups.server_id == server_id).execute()
u3 = Schedules.update(schd).where(Schedules.schedule_id == row.schedule_id).execute()
logger.debug("Updating existing backup record. {}+{}+{} rows affected".format(u1, u2, u3)) logger.debug("Updating existing backup record. {}+{}+{} rows affected".format(u1, u2, u3))
else: else:
with database.atomic(): with database.atomic():
conf["server_id"] = server_id conf["server_id"] = server_id
if backup_path is not None: if backup_path is not None:
u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id) u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
s = Schedules.create(**schd)
conf['schedule_id'] = s.schedule_id
b = Backups.create(**conf) b = Backups.create(**conf)
logger.debug("Creating new backup record.") logger.debug("Creating new backup record.")

View File

@ -1,3 +1,4 @@
from datetime import timedelta
import os import os
import sys import sys
import json import json
@ -6,6 +7,11 @@ import logging
import threading import threading
import asyncio import asyncio
import shutil import shutil
from tzlocal import get_localzone
import tzlocal
from pytz import HOUR, timezone
from app.classes.controllers.users_controller import Users_Controller
from app.classes.shared.helpers import helper from app.classes.shared.helpers import helper
from app.classes.shared.console import console from app.classes.shared.console import console
@ -15,6 +21,8 @@ from app.classes.web.websocket_helper import websocket_helper
from app.classes.minecraft.serverjars import server_jar_obj from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.models.servers import servers_helper from app.classes.models.servers import servers_helper
from app.classes.models.management import management_helper from app.classes.models.management import management_helper
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED, EVENT_ALL, EVENT_JOB_REMOVED
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -46,6 +54,10 @@ class TasksManager:
self.controller = controller self.controller = controller
self.tornado = Webserver(controller, self) self.tornado = Webserver(controller, self)
self.scheduler = BackgroundScheduler(timezone=str(tzlocal.get_localzone()))
self.users_controller = Users_Controller()
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread') self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread')
self.main_thread_exiting = False self.main_thread_exiting = False
@ -104,6 +116,8 @@ class TasksManager:
elif command == "update_executable": elif command == "update_executable":
svr.jar_update() svr.jar_update()
else:
svr.send_command(command)
management_helper.mark_command_complete(c.get('command_id', None)) management_helper.mark_command_complete(c.get('command_id', None))
time.sleep(1) time.sleep(1)
@ -147,11 +161,87 @@ class TasksManager:
console.info("Launching realtime thread...") console.info("Launching realtime thread...")
self.realtime_thread.start() self.realtime_thread.start()
@staticmethod def scheduler_thread(self):
def scheduler_thread(): schedules = management_helper.get_schedules_enabled()
while True: self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
schedule.run_pending() #self.scheduler.add_job(self.scheduler.print_jobs, 'interval', seconds=10, id='-1')
time.sleep(1) #load schedules from DB
for schedule in schedules:
if schedule.cron_string != "":
cron = schedule.cron_string.split(' ')
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
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':
time = schedule.start_time.split(':')
self.scheduler.add_job(management_helper.add_command, 'cron', day = '*/'+str(schedule.interval), hour=time[0], minute=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()
def schedule_job(self, job_data):
sch_id = management_helper.create_scheduled_task(job_data['server_id'], job_data['action'], job_data['interval'], job_data['interval_type'], job_data['time'], job_data['command'], "None", job_data['enabled'], job_data['one_time'], job_data['cron_string'])
if job_data['enabled']:
if job_data['cron_string'] != "":
cron = job_data['cron_string'].split(' ')
try:
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], 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("Failed to schedule task with error: {}.".format(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':
time = job_data['start_time'].split(':')
self.scheduler.add_job(management_helper.add_command, 'cron', day = '*/'+str(job_data['interval']), hour = time[0], minute = 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']], )
def remove_job(self, sch_id):
management_helper.delete_scheduled_task(sch_id)
self.scheduler.remove_job(str(sch_id))
def update_job(self, sch_id, job_data):
management_helper.update_scheduled_task(sch_id, job_data)
if job_data['enabled']:
self.scheduler.remove_job(str(sch_id))
if job_data['cron_string'] != "":
cron = job_data['cron_string'].split(' ')
try:
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], 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("Failed to schedule task with error: {}.".format(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':
time = job_data['start_time'].split(':')
self.scheduler.add_job(management_helper.add_command, 'cron', day = '*/'+str(job_data['interval']), hour = time[0], minute = 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))
self.scheduler.remove_job(str(sch_id))
except:
logger.info("APScheduler found no scheduled job on schedule update for schedule with id: {}. Assuming it was already disabled.".format(sch_id))
def schedule_watcher(self, event):
if not event.exception:
task = management_helper.get_scheduled_task_model(int(event.job_id))
if task.one_time:
self.remove_job(task.schedule_id)
logger.info("one time task detected. Deleting...")
else:
print("error")
logger.error("Task failed with error: {}".format(event.exception))
def start_stats_recording(self): def start_stats_recording(self):
stats_update_frequency = helper.get_setting('stats_update_frequency') stats_update_frequency = helper.get_setting('stats_update_frequency')

View File

@ -341,6 +341,13 @@ class AjaxHandler(BaseHandler):
# Delete the file # Delete the file
os.remove(file_path) os.remove(file_path)
if page == "del_task":
if not permissions['Schedule'] in user_perms:
self.redirect("/panel/error?error=Unauthorized access to Tasks")
else:
sch_id = self.get_argument('schedule_id', '-404')
self.tasks_manager.remove_job(sch_id)
if page == "del_backup": if page == "del_backup":
if not permissions['Backup'] in user_perms: if not permissions['Backup'] in user_perms:
if not exec_user['superuser']: if not exec_user['superuser']:

View File

@ -8,6 +8,7 @@ import time
import datetime import datetime
import os import os
from tornado import locale
from tornado import iostream from tornado import iostream
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
from app.classes.shared.console import console from app.classes.shared.console import console
@ -460,6 +461,98 @@ class PanelHandler(BaseHandler):
template = "panel/panel_edit_user.html" template = "panel/panel_edit_user.html"
elif page == "add_schedule":
server_id = self.get_argument('id', None)
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
page_data['active_link'] = 'tasks'
page_data['permissions'] = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
'Logs': Enum_Permissions_Server.Logs,
'Schedule': Enum_Permissions_Server.Schedule,
'Backup': Enum_Permissions_Server.Backup,
'Files': Enum_Permissions_Server.Files,
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
page_data['user_permissions'] = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
exec_user_server_permissions = self.controller.server_perms.get_user_permissions_list(exec_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['new_schedule'] = True
page_data['schedule'] = {}
page_data['schedule']['server_id'] = server_id
page_data['schedule']['schedule_id'] = ''
page_data['schedule']['action'] = ""
page_data['schedule']['enabled'] = True
page_data['schedule']['command'] = ''
page_data['schedule']['one_time'] = False
page_data['schedule']['cron_string'] = ""
page_data['schedule']['time'] = ""
page_data['schedule']['interval'] = ""
#we don't need to check difficulty here. We'll just default to basic for new schedules
page_data['schedule']['difficulty'] = "basic"
page_data['schedule']['interval_type'] = 'days'
if not Enum_Permissions_Server.Schedule in exec_user_server_permissions:
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
return
template = "panel/server_schedule_edit.html"
elif page == "edit_schedule":
server_id = self.get_argument('id', None)
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['permissions'] = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
'Logs': Enum_Permissions_Server.Logs,
'Schedule': Enum_Permissions_Server.Schedule,
'Backup': Enum_Permissions_Server.Backup,
'Files': Enum_Permissions_Server.Files,
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
page_data['user_permissions'] = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
exec_user_server_permissions = self.controller.server_perms.get_user_permissions_list(exec_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['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
#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':
page_data['schedule']['command'] = schedule.command
else:
page_data['schedule']['command'] = ''
page_data['schedule']['enabled'] = schedule.enabled
page_data['schedule']['one_time'] = schedule.one_time
page_data['schedule']['cron_string'] = schedule.cron_string
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 == '':
difficulty = 'basic'
else:
difficulty = 'advanced'
page_data['schedule']['difficulty'] = difficulty
if sch_id == None or server_id == None:
self.redirect("/panel/error?error=Invalid server ID or Schedule ID")
if not Enum_Permissions_Server.Schedule in exec_user_server_permissions:
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access To Scheduled Tasks")
return
template = "panel/server_schedule_edit.html"
elif page == "edit_user": elif page == "edit_user":
user_id = self.get_argument('id', None) user_id = self.get_argument('id', None)
role_servers = self.controller.servers.get_authorized_servers(user_id) role_servers = self.controller.servers.get_authorized_servers(user_id)
@ -801,10 +894,6 @@ class PanelHandler(BaseHandler):
else: else:
backup_path = server_obj.backup_path backup_path = server_obj.backup_path
max_backups = bleach.clean(self.get_argument('max_backups', None)) max_backups = bleach.clean(self.get_argument('max_backups', None))
try:
enabled = int(float(bleach.clean(self.get_argument('auto_enabled'), '0')))
except Exception as e:
enabled = '0'
if not permissions['Backup'] in user_perms: if not permissions['Backup'] in user_perms:
if not exec_user['superuser']: if not exec_user['superuser']:
@ -819,18 +908,10 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID") self.redirect("/panel/error?error=Invalid Server ID")
return return
if enabled == '0':
#TODO Use Controller method
server_obj = self.controller.servers.get_server_obj(server_id) server_obj = self.controller.servers.get_server_obj(server_id)
server_obj.backup_path = backup_path server_obj.backup_path = backup_path
self.controller.servers.update_server(server_obj) self.controller.servers.update_server(server_obj)
self.controller.management.set_backup_config(server_id, max_backups=max_backups, auto_enabled=False) self.controller.management.set_backup_config(server_id, max_backups=max_backups)
else:
#TODO Use Controller method
server_obj = self.controller.servers.get_server_obj(server_id)
server_obj.backup_path = backup_path
self.controller.servers.update_server(server_obj)
self.controller.management.set_backup_config(server_id, max_backups=max_backups, auto_enabled=True)
self.controller.management.add_to_audit_log(exec_user['user_id'], self.controller.management.add_to_audit_log(exec_user['user_id'],
"Edited server {}: updated backups".format(server_id), "Edited server {}: updated backups".format(server_id),
@ -839,6 +920,239 @@ class PanelHandler(BaseHandler):
self.tasks_manager.reload_schedule_from_db() self.tasks_manager.reload_schedule_from_db()
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id)) self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
if page == "add_schedule":
server_id = bleach.clean(self.get_argument('id', None))
difficulty = bleach.clean(self.get_argument('difficulty', None))
server_obj = self.controller.servers.get_server_obj(server_id)
enabled = bleach.clean(self.get_argument('enabled', '0'))
if difficulty == 'basic':
action = bleach.clean(self.get_argument('action', None))
interval = bleach.clean(self.get_argument('interval', None))
interval_type = bleach.clean(self.get_argument('interval_type', None))
#only check for time if it's number of days
if interval_type == "days":
time = bleach.clean(self.get_argument('time', 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', ''))
cron_split = cron_string.split(' ')
if len(cron_split) != 5:
self.redirect("/panel/error?error=INVALID FORMAT: Invalid Cron Format. Cron must have a space between each character and only have a max of 5 characters * * * * *")
return
action = bleach.clean(self.get_argument('action', 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"
if bleach.clean(self.get_argument('enabled', '1'))=='1':
enabled = True
else:
enabled = False
if bleach.clean(self.get_argument('one_time', '0')) == '1':
one_time = True
else:
one_time = False
if not exec_user['superuser'] and not permissions['Backup'] in user_perms:
self.redirect("/panel/error?error=Unauthorized access: User not authorized")
return
elif server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return
else:
# does this server id exist?
if not self.controller.servers.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return
minute = datetime.datetime.now().minute
hour = datetime.datetime.now().hour
if minute < 10:
minute = '0' + str(minute)
if hour < 10:
hour = '0'+str(hour)
current_time = str(hour)+':'+str(minute)
if interval_type == "days":
job_data = {
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"start_time": time,
"enabled": enabled,
"one_time": one_time,
"cron_string": ''
}
elif difficulty == "advanced":
job_data = {
"server_id": server_id,
"action": action,
"interval_type": '',
"interval": '',
"command": '',
"start_time": current_time,
"command": command,
"cron_string": cron_string,
"enabled": enabled,
"one_time": one_time
}
else:
job_data = {
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"enabled": enabled,
"start_time": current_time,
"one_time": one_time,
"cron_string": ''
}
self.tasks_manager.schedule_job(job_data)
self.controller.management.add_to_audit_log(exec_user['user_id'],
"Edited server {}: added scheduled job".format(server_id),
server_id,
self.get_remote_ip())
self.tasks_manager.reload_schedule_from_db()
self.redirect("/panel/server_detail?id={}&subpage=tasks".format(server_id))
if page == "edit_schedule":
server_id = bleach.clean(self.get_argument('id', None))
difficulty = bleach.clean(self.get_argument('difficulty', None))
server_obj = self.controller.servers.get_server_obj(server_id)
enabled = bleach.clean(self.get_argument('enabled', '0'))
if difficulty == 'basic':
action = bleach.clean(self.get_argument('action', None))
interval = bleach.clean(self.get_argument('interval', None))
interval_type = bleach.clean(self.get_argument('interval_type', None))
#only check for time if it's number of days
if interval_type == "days":
time = bleach.clean(self.get_argument('time', 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', ''))
sch_id = self.get_argument('sch_id', None)
if len(cron_string.split(' ')) != 5:
self.redirect("/panel/error?error=INVALID FORMAT: Invalid Cron Format. Cron must have a space between each character and only have a max of 5 characters * * * * *")
return
action = bleach.clean(self.get_argument('action', 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"
if bleach.clean(self.get_argument('enabled', '1'))=='1':
enabled = True
else:
enabled = False
if bleach.clean(self.get_argument('one_time', '0')) == '1':
one_time = True
else:
one_time = False
if not exec_user['superuser'] and not permissions['Backup'] in user_perms:
self.redirect("/panel/error?error=Unauthorized access: User not authorized")
return
elif server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return
else:
# does this server id exist?
if not self.controller.servers.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return
minute = datetime.datetime.now().minute
hour = datetime.datetime.now().hour
if minute < 10:
minute = '0' + str(minute)
if hour < 10:
hour = '0'+str(hour)
current_time = str(hour)+':'+str(minute)
if interval_type == "days":
job_data = {
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"start_time": time,
"enabled": enabled,
"one_time": one_time,
"cron_string": ''
}
elif difficulty == "advanced":
job_data = {
"server_id": server_id,
"action": action,
"interval_type": '',
"interval": '',
"command": '',
"start_time": current_time,
"command": command,
"cron_string": cron_string,
"enabled": enabled,
"one_time": one_time
}
else:
job_data = {
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"enabled": enabled,
"start_time": current_time,
"one_time": one_time,
"cron_string": ''
}
sch_id = self.get_argument('sch_id', None)
self.tasks_manager.update_job(sch_id, job_data)
self.controller.management.add_to_audit_log(exec_user['user_id'],
"Edited server {}: updated schedule".format(server_id),
server_id,
self.get_remote_ip())
self.tasks_manager.reload_schedule_from_db()
self.redirect("/panel/server_detail?id={}&subpage=tasks".format(server_id))
elif page == "edit_user": elif page == "edit_user":
if bleach.clean(self.get_argument('username', None)) == 'system': if bleach.clean(self.get_argument('username', None)) == 'system':
self.redirect("/panel/error?error=Unauthorized access: system user is not editable") self.redirect("/panel/error?error=Unauthorized access: system user is not editable")
@ -1144,8 +1458,8 @@ class PanelHandler(BaseHandler):
else: else:
self.set_status(404) self.set_status(404)
page_data = [] page_data = {}
page_data['lang'] = tornado.locale.get("en_EN") page_data['lang'] = locale.get("en_EN")
self.render( self.render(
"public/404.html", "public/404.html",
translate=self.translator.translate, translate=self.translator.translate,

View File

@ -13,7 +13,7 @@
{% end %} {% end %}
{% if data['permissions']['Schedule'] in data['user_permissions'] %} {% if data['permissions']['Schedule'] in data['user_permissions'] %}
<li class="nav-item term-nav-item"> <li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'schedule' %}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'] == 'tasks' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule', data['lang']) }}</a> <i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule', data['lang']) }}</a>
</li> </li>
{% end %} {% end %}

View File

@ -56,16 +56,6 @@
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}" > <input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}" >
</div> </div>
<div class="form-group">
<label for="superuser" class="form-check-label ml-4 mb-4">
{% if data['backup_config']['auto_enabled'] %}
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" checked="" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
{% end %}
</label>
</div>
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang']) }}</button> <button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang']) }}</button>
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button> <button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
</form> </form>

View File

@ -0,0 +1,251 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
{% include "parts/details_stats.html %}
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
{% include "parts/server_controls_list.html %}
<div class="row">
<div class="col-md-12 col-sm-12">
{% if data['new_schedule'] == True %}
<form class="forms-sample" method="post" action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
{% else %}
<form class="forms-sample" method="post" action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
{% end %}
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="difficulty">Basic / Cron 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>
</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="shutdown" value="shutdown">Shutdown Server</option>
<option id="backup" value="backup">Backup Server</option>
<option id="command" value="command">Custon Command</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>
<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>
</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>
<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>
<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>
<input type="input" class="form-control" name="cron" id="cron" value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
</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
</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
</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>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$( document ).ready(function() {
console.log( "ready!" );
});
function yesnoCheck() {
if (document.getElementById('command').value == "command") {
document.getElementById("ifYes").style.display = "block";
document.getElementById("command").required = true;
} else {
document.getElementById("ifYes").style.display = "none";
document.getElementById("command").required = false;
}
}
function basicAdvanced() {
if (document.getElementById('difficulty').value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
} else {
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifBasic").style.display = "block";
document.getElementById("interval").required = true;
document.getElementById("time").required = true;
}
}
function ifDays() {
if (document.getElementById('interval_type').value == "days") {
document.getElementById("ifDays").style.display = "block";
document.getElementById("time").required = true;
} else {
document.getElementById("ifDays").style.display = "none";
document.getElementById("time").required = false;
}
}
$( ".del_button" ).click(function() {
var sch_id = $(this).data('sch');
var server_id = {{ data['server_stats']['server_id']['server_id'] }};
console.log(sch_id)
bootbox.confirm({
title: "Test",
message: "{{ translate('serverBackups', 'confirmDelete', data['lang']) }}",
buttons: {
cancel: {
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
},
confirm: {
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
}
},
callback: function (result) {
console.log(result);
if (result == true) {
del_task(sch_id, server_id);
}
}
});
});
function del_task(sch_id, id){
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id,
data: {
schedule_id: sch_id,
id: id
},
success: function(data) {
location.reload();
},
});
}
function startup(){
try{
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);
}catch{
console.log("no element named {{ data['schedule']['interval_type'] }}")
}
try{
document.getElementById("{{ data['schedule']['difficulty'] }}").setAttribute('selected', true);
}catch{
console.log("no element named {{ data['schedule']['difficulty'] }}")
}
try{
document.getElementById("{{ data['schedule']['action'] }}").setAttribute('selected', true);
}catch{
console.log("no element named {{ data['schedule']['action'] }}")
}
console.log("in on load")
yesnoCheck();
basicAdvanced();
ifDays();
if("{{ data['schedule']['enabled'] }}" == 'True'){
document.getElementById('enabled').checked = true;
}else{
document.getElementById('enabled').checked = true;
}
if("{{ data['schedule']['one_time'] }}" == 'True'){
document.getElementById('one_time').checked = true;
}else{
document.getElementById('one_time').checked = false;
}
}
window.onload(startup())
</script>
{% end %}

View File

@ -35,98 +35,39 @@
{% include "parts/server_controls_list.html %} {% include "parts/server_controls_list.html %}
<div class="row"> <div class="row">
<div class="col-md-4 col-sm-12"> <button onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`" class="btn btn-info">Create New Schedule <i class="fas fa-pencil-alt"></i></button>
<form class="forms-sample" method="post" action="/panel/server_detail"> <div class="col-md-12 col-sm-12" style="overflow-x:auto;">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="server_name">Basic / Cron Select<small class="text-muted ml-1"></small> </label><br>
<select id="action" name="action" onchange="basicAdvanced(this);" class="form-control form-control-lg select-css">
<option value="basic">Basic</option>
<option value="advanced">Advanced</option>
</select>
</div>
<div id="ifBasic">
<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">
<option value="start">Start Server</option>
<option value="restart">Restart Server</option>
<option value="shutdown">Shutdown Server</option>
<option value="command">Custon Command</option>
</select>
</div>
<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>
<input type="number" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="Interval" required>
<br>
<br>
<select id="interval_type" name="interval_type" class="form-control form-control-lg select-css">
<option value="days">Days</option>
<option value="hours">Hours</option>
<option value="minutes">Minutes</option>
<option value="weeks">Weeks</option>
</select>
</div>
<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>
<input type="time" class="form-control" name="time" id="time" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="Time" required>
</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>
<input type="input" class="form-control" name="command" id="command" value="" placeholder="Command" required>
</div>
</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>
<input type="input" class="form-control" name="cron" id="cron" value="" placeholder="* * * * backup_server" required>
</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
</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
</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" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
</form>
</div>
<div class="col-md-8 col-sm-12">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">Scheduled Tasks</h4> <h4 class="card-title">Scheduled Tasks</h4>
<table class="table table-hover" width="100%"> <table class="table table-hover" width="100%">
<thead>
<tr class="rounded"> <tr class="rounded">
<th>Action</th> <th>Action</th>
<th>Command</th>
<th>Interval</th> <th>Interval</th>
<th>Start Time</th> <th>Start Time</th>
<th>Enabled</th> <th>Enabled</th>
<th>Edit</th> <th>Edit</th>
</tr> </tr>
</thead>
<tbody>
{% for schedule in data['schedules'] %} {% for schedule in data['schedules'] %}
<tr>
<td id="{{schedule.action}}" class="action"> <td id="{{schedule.action}}" class="action">
<p>{{schedule.action}}</p> <p>{{schedule.action}}</p>
</td> </td>
<td id="{{schedule.command}}" class="action">
<p>{{schedule.command}}</p>
</td>
<td id="{{schedule.interval}}" class="action"> <td id="{{schedule.interval}}" class="action">
{% if schedule.interval != '' %}
<p>Every</p> <p>Every</p>
<p>{{schedule.interval}} {{schedule.interval_type}}</p> <p>{{schedule.interval}} {{schedule.interval_type}}</p>
{% else %}
<p>Cron String:</p>
<p>{{schedule.cron_string}}</p>
{% end %}
</td> </td>
<td id="{{schedule.start_time}}" class="action"> <td id="{{schedule.start_time}}" class="action">
<p>{{schedule.start_time}}</p> <p>{{schedule.start_time}}</p>
@ -143,11 +84,18 @@
</td> </td>
{% end %} {% end %}
<td id="{{schedule.action}}" class="action"> <td id="{{schedule.action}}" class="action">
<a href="/panel/edit_schedule?id={{schedule.schedule_id}}"><i class="fas fa-pencil-alt"></i></a> <button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
<i class="fas fa-pencil-alt"></i>
</button>
<br>
<br>
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i>
</button>
</td> </td>
</tr>
{% end %} {% end %}
</tbody>
</table> </table>
</div> </div>
</div> </div>
@ -184,19 +132,77 @@
function yesnoCheck(that) { function yesnoCheck(that) {
if (that.value == "command") { if (that.value == "command") {
document.getElementById("ifYes").style.display = "block"; document.getElementById("ifYes").style.display = "block";
document.getElementById("command").required = true;
} else { } else {
document.getElementById("ifYes").style.display = "none"; document.getElementById("ifYes").style.display = "none";
document.getElementById("command").required = false;
} }
} }
function basicAdvanced(that) { function basicAdvanced(that) {
if (that.value == "advanced") { if (that.value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block"; document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifBasic").style.display = "none"; document.getElementById("ifBasic").style.display = "none";
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
} else { } else {
document.getElementById("ifAdvanced").style.display = "none"; document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifBasic").style.display = "block"; document.getElementById("ifBasic").style.display = "block";
document.getElementById("interval").required = true;
document.getElementById("time").required = true;
} }
} }
function ifDays(that) {
if (that.value == "days") {
document.getElementById("ifDays").style.display = "block";
document.getElementById("time").required = true;
} else {
document.getElementById("ifDays").style.display = "none";
document.getElementById("time").required = false;
}
}
$( ".del_button" ).click(function() {
var sch_id = $(this).data('sch');
var server_id = {{ data['server_stats']['server_id']['server_id'] }};
console.log(sch_id)
bootbox.confirm({
title: "Test",
message: "{{ translate('serverBackups', 'confirmDelete', data['lang']) }}",
buttons: {
cancel: {
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
},
confirm: {
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
}
},
callback: function (result) {
console.log(result);
if (result == true) {
del_task(sch_id, server_id);
}
}
});
});
function del_task(sch_id, id){
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id,
data: {
schedule_id: sch_id,
id: id
},
success: function(data) {
location.reload();
},
});
}
</script> </script>

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('schedules', cron_string=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('schedules', ['cron_string'])
"""
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('schedules', one_time=peewee.BooleanField(default=False))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('schedules', ['one_time'])
"""
Write your rollback migrations here.
"""