Add a basic API for modifying schedules.

THIS IS VERY UNTESTED AND WILL BE EXPANDED TO FULL CRUD FOR SCHEDULES
This commit is contained in:
luukas 2022-06-23 01:57:29 +03:00
parent 81bc228e40
commit 147f178c87
No known key found for this signature in database
GPG Key ID: CC4915E8D71FC044
10 changed files with 255 additions and 124 deletions

View File

@ -278,11 +278,13 @@ class TasksManager:
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):
HelpersManagement.update_scheduled_task(sch_id, {"parent": None})
# Check to see if it's enabled and is not a chain reaction.
# 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:
@ -379,11 +381,21 @@ class TasksManager:
)
def update_job(self, sch_id, job_data):
HelpersManagement.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):
HelpersManagement.update_scheduled_task(sch_id, {"parent": None})
if str(job_data.get("parent")) == str(sch_id):
job_data["parent"] = None
HelpersManagement.update_scheduled_task(sch_id, job_data)
if not (
"interval" in job_data
and "enabled" in job_data
and "cron_string" in job_data
and "interval_type" in job_data
):
return
try:
if job_data["interval"] != "reaction":
self.scheduler.remove_job(str(sch_id))
@ -393,71 +405,70 @@ class TasksManager:
"Assuming it was previously disabled. Starting new job."
)
if job_data["enabled"]:
if job_data["interval"] != "reaction":
if job_data["cron_string"] != "":
try:
self.scheduler.add_job(
HelpersManagement.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.")
self.controller.management_helper.delete_scheduled_task(sch_id)
else:
if job_data["interval_type"] == "hours":
self.scheduler.add_job(
HelpersManagement.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(
HelpersManagement.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(
HelpersManagement.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["enabled"] and job_data["interval"] != "reaction":
if job_data["cron_string"] != "":
try:
self.scheduler.add_job(
HelpersManagement.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.")
self.controller.management_helper.delete_scheduled_task(sch_id)
else:
if job_data["interval_type"] == "hours":
self.scheduler.add_job(
HelpersManagement.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(
HelpersManagement.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(
HelpersManagement.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))

View File

@ -531,6 +531,7 @@ class PanelHandler(BaseHandler):
page_data["downloading"] = self.controller.servers.get_download_status(
server_id
)
page_data["server_id"] = server_id
try:
page_data["waiting_start"] = self.controller.servers.get_waiting_start(
server_id

View File

@ -23,6 +23,15 @@ from app.classes.web.routes.api.servers.server.public import (
)
from app.classes.web.routes.api.servers.server.stats import ApiServersServerStatsHandler
from app.classes.web.routes.api.servers.server.stdin import ApiServersServerStdinHandler
from app.classes.web.routes.api.servers.server.tasks.index import (
ApiServersServerTasksIndexHandler,
)
from app.classes.web.routes.api.servers.server.tasks.task.children import (
ApiServersServerTasksTaskChildrenHandler,
)
from app.classes.web.routes.api.servers.server.tasks.task.index import (
ApiServersServerTasksTaskIndexHandler,
)
from app.classes.web.routes.api.servers.server.users import ApiServersServerUsersHandler
from app.classes.web.routes.api.users.index import ApiUsersIndexHandler
from app.classes.web.routes.api.users.user.index import ApiUsersUserIndexHandler
@ -103,6 +112,21 @@ def api_handlers(handler_args):
ApiServersServerIndexHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/tasks/?",
ApiServersServerTasksIndexHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/tasks/([0-9]+)/?",
ApiServersServerTasksTaskIndexHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/tasks/([0-9]+)/children/?",
ApiServersServerTasksTaskChildrenHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/stats/?",
ApiServersServerStatsHandler,

View File

@ -5,6 +5,7 @@ from app.classes.web.routes.api.roles.role.index import modify_role_schema
from app.classes.web.routes.api.roles.index import create_role_schema
from app.classes.web.routes.api.servers.server.index import server_patch_schema
from app.classes.web.routes.api.servers.index import new_server_schema
from app.classes.web.routes.api.servers.server.tasks.task.index import task_patch_schema
SCHEMA_LIST: t.Final = [
"login",
@ -14,6 +15,7 @@ SCHEMA_LIST: t.Final = [
"new_server",
"user_patch",
"new_user",
"task_patch",
]
@ -59,22 +61,8 @@ class ApiJsonSchemaHandler(BaseApiHandler):
"properties": {
**self.controller.users.user_jsonschema_props,
},
"anyOf": [
# Require at least one property
{"required": [name]}
for name in [
"username",
"password",
"email",
"enabled",
"lang",
"superuser",
"permissions",
"roles",
"hints",
]
],
"additionalProperties": False,
"minProperties": 1,
},
},
)
@ -93,6 +81,11 @@ class ApiJsonSchemaHandler(BaseApiHandler):
},
},
)
elif schema_name == "task_patch":
self.finish_json(
200,
{"status": "ok", "data": task_patch_schema},
)
else:
self.finish_json(
404,

View File

@ -28,11 +28,8 @@ modify_role_schema = {
},
},
},
"anyOf": [
{"required": ["name"]},
{"required": ["servers"]},
],
"additionalProperties": False,
"minProperties": 1,
}

View File

@ -28,28 +28,8 @@ server_patch_schema = {
"logs_delete_after": {"type": "integer"},
"type": {"type": "string", "minLength": 1},
},
"anyOf": [
# Require at least one property
{"required": [name]}
for name in [
"server_name",
"path",
"backup_path",
"executable",
"log_path",
"execution_command",
"auto_start",
"auto_start_delay",
"crash_detection",
"stop_command",
"executable_update_url",
"server_ip",
"server_port",
"logs_delete_after",
"type",
]
],
"additionalProperties": False,
"minProperties": 1,
}

View File

@ -0,0 +1,16 @@
# TODO: create and read
import logging
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiServersServerTasksIndexHandler(BaseApiHandler):
def get(self, server_id: str, task_id: str):
pass
def post(self, server_id: str, task_id: str):
pass

View File

@ -0,0 +1,13 @@
# TODO: read
import logging
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiServersServerTasksTaskChildrenHandler(BaseApiHandler):
def get(self, server_id: str, task_id: str):
pass

View File

@ -0,0 +1,110 @@
# TODO: read and delete
import json
import logging
from jsonschema import ValidationError, validate
from app.classes.models.management import HelpersManagement
from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
task_patch_schema = {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": True,
},
"action": {
"type": "string",
},
"interval": {"type": "integer"},
"interval_type": {
"type": "string",
"enum": [
# Basic tasks
"hours",
"minutes",
"days",
# Chain reaction tasks:
"reaction",
# CRON tasks:
"",
],
},
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
"command": {"type": ["string", "null"]},
"one_time": {"type": "boolean", "default": False},
"cron_string": {"type": "string", "default": ""},
"parent": {"type": ["integer", "null"]},
"delay": {"type": "integer", "default": 0},
},
"additionalProperties": False,
"minProperties": 1,
}
class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
def get(self, server_id: str, task_id: str):
pass
def delete(self, server_id: str, task_id: str):
pass
def patch(self, server_id: str, task_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, task_patch_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.SCHEDULE
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
# Checks to make sure some doofus didn't actually make the newly
# created task a child of itself.
if str(data.get("parent")) == str(task_id) and data.get("parent") is not None:
data["parent"] = None
HelpersManagement.update_scheduled_task(task_id, data)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"Edited server {server_id}: updated schedule",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.reload_schedule_from_db()
self.finish_json(200, {"status": "ok"})

View File

@ -112,22 +112,8 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
"properties": {
**self.controller.users.user_jsonschema_props,
},
"anyOf": [
# Require at least one property
{"required": [name]}
for name in [
"username",
"password",
"email",
"enabled",
"lang",
"superuser",
"permissions",
"roles",
"hints",
]
],
"additionalProperties": False,
"minProperties": 1,
}
auth_data = self.authenticate_user()
if not auth_data: