Merge branch 'enhancement/pretzel-next-run' into 'dev'

Add next run to schedule info

See merge request crafty-controller/crafty-4!417
This commit is contained in:
Iain Powrie 2022-08-05 00:46:45 +00:00
commit bc8ec179a1
5 changed files with 109 additions and 36 deletions

View File

@ -10,7 +10,7 @@ TBD
- Fix server creation with serverjars API ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/415)) - Fix server creation with serverjars API ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/415))
- Fix API Key delete confirmations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/416)) - Fix API Key delete confirmations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/416))
### Tweaks ### Tweaks
TBD - Add next run to schedule info ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/417))
### Lang ### Lang
- Updated `es_ES` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/412)) - Updated `es_ES` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/412))
- Added `pl_PL` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/412)) - Added `pl_PL` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/412))

View File

@ -115,6 +115,7 @@ class Schedules(BaseModel):
cron_string = CharField(default="") cron_string = CharField(default="")
parent = IntegerField(null=True) parent = IntegerField(null=True)
delay = IntegerField(default=0) delay = IntegerField(default=0)
next_run = CharField(default="")
class Meta: class Meta:
table_name = "schedules" table_name = "schedules"
@ -285,6 +286,7 @@ class HelpersManagement:
Schedules.cron_string: cron_string, Schedules.cron_string: cron_string,
Schedules.parent: parent, Schedules.parent: parent,
Schedules.delay: delay, Schedules.delay: delay,
Schedules.next_run: "",
} }
).execute() ).execute()
return sch_id return sch_id

View File

@ -192,6 +192,7 @@ class TasksManager:
def scheduler_thread(self): def scheduler_thread(self):
schedules = HelpersManagement.get_schedules_enabled() schedules = HelpersManagement.get_schedules_enabled()
self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED) self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
self.scheduler.start()
# self.scheduler.add_job( # self.scheduler.add_job(
# self.scheduler.print_jobs, "interval", seconds=10, id="-1" # self.scheduler.print_jobs, "interval", seconds=10, id="-1"
# ) # )
@ -201,7 +202,7 @@ class TasksManager:
if schedule.interval != "reaction": if schedule.interval != "reaction":
if schedule.cron_string != "": if schedule.cron_string != "":
try: try:
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
CronTrigger.from_crontab( CronTrigger.from_crontab(
schedule.cron_string, timezone=str(self.tz) schedule.cron_string, timezone=str(self.tz)
@ -215,6 +216,7 @@ class TasksManager:
], ],
) )
except Exception as e: except Exception as e:
new_job = "error"
Console.error(f"Failed to schedule task with error: {e}.") Console.error(f"Failed to schedule task with error: {e}.")
Console.warning("Removing failed task from DB.") Console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.") logger.error(f"Failed to schedule task with error: {e}.")
@ -225,7 +227,7 @@ class TasksManager:
) )
else: else:
if schedule.interval_type == "hours": if schedule.interval_type == "hours":
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
minute=0, minute=0,
@ -239,7 +241,7 @@ class TasksManager:
], ],
) )
elif schedule.interval_type == "minutes": elif schedule.interval_type == "minutes":
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
minute="*/" + str(schedule.interval), minute="*/" + str(schedule.interval),
@ -253,7 +255,7 @@ class TasksManager:
) )
elif schedule.interval_type == "days": elif schedule.interval_type == "days":
curr_time = schedule.start_time.split(":") curr_time = schedule.start_time.split(":")
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
day="*/" + str(schedule.interval), day="*/" + str(schedule.interval),
@ -267,7 +269,18 @@ class TasksManager:
schedule.command, schedule.command,
], ],
) )
self.scheduler.start() if new_job != "error":
task = self.controller.management.get_scheduled_task_model(
int(new_job.id)
)
self.controller.management.update_scheduled_task(
task.schedule_id,
{
"next_run": str(
new_job.next_run_time.strftime("%m/%d/%Y, %H:%M:%S")
)
},
)
jobs = self.scheduler.get_jobs() jobs = self.scheduler.get_jobs()
logger.info("Loaded schedules. Current enabled schedules: ") logger.info("Loaded schedules. Current enabled schedules: ")
for item in jobs: for item in jobs:
@ -298,7 +311,7 @@ class TasksManager:
if job_data["enabled"] and job_data["interval_type"] != "reaction": if job_data["enabled"] and job_data["interval_type"] != "reaction":
if job_data["cron_string"] != "": if job_data["cron_string"] != "":
try: try:
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
CronTrigger.from_crontab( CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz) job_data["cron_string"], timezone=str(self.tz)
@ -312,6 +325,7 @@ class TasksManager:
], ],
) )
except Exception as e: except Exception as e:
new_job = "error"
Console.error(f"Failed to schedule task with error: {e}.") Console.error(f"Failed to schedule task with error: {e}.")
Console.warning("Removing failed task from DB.") Console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.") logger.error(f"Failed to schedule task with error: {e}.")
@ -320,7 +334,7 @@ class TasksManager:
self.controller.management_helper.delete_scheduled_task(sch_id) self.controller.management_helper.delete_scheduled_task(sch_id)
else: else:
if job_data["interval_type"] == "hours": if job_data["interval_type"] == "hours":
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
minute=0, minute=0,
@ -334,7 +348,7 @@ class TasksManager:
], ],
) )
elif job_data["interval_type"] == "minutes": elif job_data["interval_type"] == "minutes":
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
minute="*/" + str(job_data["interval"]), minute="*/" + str(job_data["interval"]),
@ -348,7 +362,7 @@ class TasksManager:
) )
elif job_data["interval_type"] == "days": elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":") curr_time = job_data["start_time"].split(":")
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
day="*/" + str(job_data["interval"]), day="*/" + str(job_data["interval"]),
@ -364,6 +378,14 @@ class TasksManager:
) )
logger.info("Added job. Current enabled schedules: ") logger.info("Added job. Current enabled schedules: ")
jobs = self.scheduler.get_jobs() jobs = self.scheduler.get_jobs()
if new_job != "error":
task = self.controller.management.get_scheduled_task_model(
int(new_job.id)
)
self.controller.management.update_scheduled_task(
task.schedule_id,
{"next_run": new_job.next_run_time.strftime("%m/%d/%Y, %H:%M:%S")},
)
for item in jobs: for item in jobs:
logger.info(f"JOB: {item}") logger.info(f"JOB: {item}")
@ -418,7 +440,7 @@ class TasksManager:
if job_data["enabled"] and job_data["interval"] != "reaction": if job_data["enabled"] and job_data["interval"] != "reaction":
if job_data["cron_string"] != "": if job_data["cron_string"] != "":
try: try:
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
CronTrigger.from_crontab( CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz) job_data["cron_string"], timezone=str(self.tz)
@ -432,12 +454,13 @@ class TasksManager:
], ],
) )
except Exception as e: except Exception as e:
new_job = "error"
Console.error(f"Failed to schedule task with error: {e}.") Console.error(f"Failed to schedule task with error: {e}.")
Console.info("Removing failed task from DB.") Console.info("Removing failed task from DB.")
self.controller.management_helper.delete_scheduled_task(sch_id) self.controller.management_helper.delete_scheduled_task(sch_id)
else: else:
if job_data["interval_type"] == "hours": if job_data["interval_type"] == "hours":
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
minute=0, minute=0,
@ -451,7 +474,7 @@ class TasksManager:
], ],
) )
elif job_data["interval_type"] == "minutes": elif job_data["interval_type"] == "minutes":
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
minute="*/" + str(job_data["interval"]), minute="*/" + str(job_data["interval"]),
@ -465,7 +488,7 @@ class TasksManager:
) )
elif job_data["interval_type"] == "days": elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":") curr_time = job_data["start_time"].split(":")
self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, HelpersManagement.add_command,
"cron", "cron",
day="*/" + str(job_data["interval"]), day="*/" + str(job_data["interval"]),
@ -479,6 +502,14 @@ class TasksManager:
job_data["command"], job_data["command"],
], ],
) )
if new_job != "error":
task = self.controller.management.get_scheduled_task_model(
int(new_job.id)
)
self.controller.management.update_scheduled_task(
task.schedule_id,
{"next_run": new_job.next_run_time.strftime("%m/%d/%Y, %H:%M:%S")},
)
else: else:
try: try:
self.scheduler.get_job(str(sch_id)) self.scheduler.get_job(str(sch_id))
@ -506,6 +537,15 @@ class TasksManager:
if task.one_time: if task.one_time:
self.remove_job(task.schedule_id) self.remove_job(task.schedule_id)
logger.info("one time task detected. Deleting...") logger.info("one time task detected. Deleting...")
else:
self.controller.management.update_scheduled_task(
task.schedule_id,
{
"next_run": self.scheduler.get_job(
event.job_id
).next_run_time.strftime("%m/%d/%Y, %H:%M:%S")
},
)
# check for any child tasks for this. It's kind of backward, # check for any child tasks for this. It's kind of backward,
# but this makes DB management a lot easier. One to one # but this makes DB management a lot easier. One to one
# instead of one to many. # instead of one to many.

View File

@ -14,7 +14,8 @@
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title"> <h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }} {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4> </h4>
@ -46,23 +47,23 @@
<h4 class="card-title"><i class="fas fa-calendar"></i> Scheduled Tasks</h4> <h4 class="card-title"><i class="fas fa-calendar"></i> Scheduled Tasks</h4>
{% if data['user_data']['hints'] %} {% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" , <span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" , data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" ,
data-placement="bottom"></span> data-placement="bottom"></span>
{% end %} {% end %}
<div><button <div><button
onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`" 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></div> class="btn btn-info">Create New Schedule <i class="fas fa-pencil-alt"></i></button></div>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" <table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%"
style="table-layout:fixed;"> style="table-layout:fixed;">
<thead> <thead>
<tr class="rounded"> <tr class="rounded">
<th style="width: 2%; min-width: 10px;">ID</th> <th style="width: 2%; min-width: 10px;">ID</th>
<th style="width: 23%; min-width: 50px;">Action</th> <th style="width: 23%; min-width: 50px;">Action</th>
<th style="width: 40%; min-width: 50px;">Command</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;">Interval</th>
<th style="width: 10%; min-width: 50px;">Start Time</th> <th style="width: 10%; min-width: 50px;">Next Run</th>
<th style="width: 10%; min-width: 50px;">Enabled</th> <th style="width: 10%; min-width: 50px;">Enabled</th>
<th style="width: 10%; min-width: 50px;">Edit</th> <th style="width: 10%; min-width: 50px;">Edit</th>
</tr> </tr>
@ -91,13 +92,17 @@
{% end %} {% end %}
</td> </td>
<td id="{{schedule.start_time}}" class="action"> <td id="{{schedule.start_time}}" class="action">
<p>{{schedule.start_time}}</p> <p>{{schedule.next_run}}</p>
</td> </td>
<td id="{{schedule.enabled}}" class="action"> <td id="{{schedule.enabled}}" class="action">
<input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}"> <input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle"
data-schedule-id="{{schedule.schedule_id}}"
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
</td> </td>
<td id="{{schedule.action}}" class="action"> <td id="{{schedule.action}}" class="action">
<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"> <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> <i class="fas fa-pencil-alt"></i>
</button> </button>
<br> <br>
@ -111,7 +116,8 @@
</tbody> </tbody>
</table> </table>
<hr /> <hr />
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" style="table-layout:fixed;"> <table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%"
style="table-layout:fixed;">
<thead> <thead>
<tr class="rounded"> <tr class="rounded">
<th style="width: 25%; min-width: 50px;">Action</th> <th style="width: 25%; min-width: 50px;">Action</th>
@ -141,7 +147,8 @@
</td> </td>
</tr> </tr>
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -177,17 +184,22 @@
{% end %} {% end %}
</li> </li>
<li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;"> <li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;">
<h4>Start Time</h4> <h4>Next Run</h4>
<p>{{schedule.start_time}}</p> <p>{{schedule.next_run}}</p>
</li> </li>
<li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray"> <li id="{{schedule.enabled}}" class="action"
style="border-top: .1em solid gray; border-bottom: .1em solid gray">
<h4>Enabled</h4> <h4>Enabled</h4>
<input type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}"> <input type="checkbox" class="schedule-enabled-toggle"
data-schedule-id="{{schedule.schedule_id}}"
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
</li> </li>
</ul> </ul>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<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"> <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> Edit <i class="fas fa-pencil-alt"></i> Edit
</button> </button>
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button"> <button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
@ -215,12 +227,15 @@
color: white !important; color: white !important;
; ;
} }
.toggle-handle { .toggle-handle {
background-color: white !important; background-color: white !important;
} }
.toggle-on { .toggle-on {
color: black !important; color: black !important;
} }
.toggle { .toggle {
height: 0px !important; height: 0px !important;
} }
@ -250,7 +265,7 @@
{% block js %} {% block js %}
<script> <script>
function debounce(func, timeout = 300){ function debounce(func, timeout = 300) {
let timer; let timer;
return (...args) => { return (...args) => {
clearTimeout(timer); clearTimeout(timer);
@ -265,17 +280,17 @@
onstyle: 'success', onstyle: 'success',
offstyle: 'danger', offstyle: 'danger',
}) })
$('.schedule-enabled-toggle').each(function() { $('.schedule-enabled-toggle').each(function () {
const enabled = JSON.parse(this.getAttribute('data-schedule-enabled')); const enabled = JSON.parse(this.getAttribute('data-schedule-enabled'));
$(this).bootstrapToggle(enabled ? 'on' : 'off') $(this).bootstrapToggle(enabled ? 'on' : 'off')
}) })
$('.schedule-enabled-toggle').change(function() { $('.schedule-enabled-toggle').change(function () {
const id = this.getAttribute('data-schedule-id'); const id = this.getAttribute('data-schedule-id');
const enabled = this.checked; const enabled = this.checked;
fetch(`/api/v2/servers/{{data['server_id']}}/tasks/${id}`, { fetch(`/api/v2/servers/{{data['server_id']}}/tasks/${id}`, {
method: 'PATCH', method: 'PATCH',
body: JSON.stringify({enabled}), body: JSON.stringify({ enabled }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },

View File

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