mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2025-01-19 09:45:28 +01:00
Merge branch 'feature/ajax-schedule-enabled' into 'dev'
Add a schedule toggle See merge request crafty-controller/crafty-4!398
This commit is contained in:
commit
b69cdd757e
@ -2,7 +2,8 @@
|
||||
|
||||
## --- [4.0.6] - 2022/07/06
|
||||
### New features
|
||||
None
|
||||
- Task toggle (!398+)
|
||||
- Basic API for modifying tasks (!398+)
|
||||
### Bug fixes
|
||||
- Remove redundant path check on backup restore ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/390))
|
||||
- Fix issue with stats pinging on slow starting servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/391))
|
||||
|
@ -288,11 +288,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:
|
||||
@ -389,11 +391,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))
|
||||
@ -403,71 +415,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))
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -28,11 +28,8 @@ modify_role_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"anyOf": [
|
||||
{"required": ["name"]},
|
||||
{"required": ["servers"]},
|
||||
],
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
16
app/classes/web/routes/api/servers/server/tasks/index.py
Normal file
16
app/classes/web/routes/api/servers/server/tasks/index.py
Normal 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
|
@ -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
|
110
app/classes/web/routes/api/servers/server/tasks/task/index.py
Normal file
110
app/classes/web/routes/api/servers/server/tasks/task/index.py
Normal 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"})
|
@ -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:
|
||||
|
@ -37,6 +37,11 @@
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<!-- End Alpine.js -->
|
||||
|
||||
<!-- Bootstrap Toggle -->
|
||||
<link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet">
|
||||
<script defer src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script>
|
||||
<!-- End Bootstrap Toggle -->
|
||||
|
||||
</head>
|
||||
|
||||
<body class="dark-theme">
|
||||
|
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
{% include "parts/details_stats.html" %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
@ -33,10 +33,10 @@
|
||||
<div class="card-body pt-0">
|
||||
|
||||
<span class="d-none d-sm-block">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
{% include "parts/server_controls_list.html" %}
|
||||
</span>
|
||||
<span class="d-block d-sm-none">
|
||||
{% include "parts/m_server_controls_list.html %}
|
||||
{% include "parts/m_server_controls_list.html" %}
|
||||
</span>
|
||||
|
||||
<div class="row">
|
||||
@ -94,15 +94,7 @@
|
||||
<p>{{schedule.start_time}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.enabled}}" class="action">
|
||||
{% if schedule.enabled %}
|
||||
<span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
<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 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">
|
||||
@ -189,15 +181,8 @@
|
||||
<p>{{schedule.start_time}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
|
||||
{% if schedule.enabled %}
|
||||
<h4>Enabled</h4> <span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<h4>Enabled</h4> <span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
<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' }}">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -230,6 +215,15 @@
|
||||
color: white !important;
|
||||
;
|
||||
}
|
||||
.toggle-handle {
|
||||
background-color: white !important;
|
||||
}
|
||||
.toggle-on {
|
||||
color: black !important;
|
||||
}
|
||||
.toggle {
|
||||
height: 0px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -256,6 +250,39 @@
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
function debounce(func, timeout = 300){
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { func.apply(this, args); }, timeout);
|
||||
};
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$('.schedule-enabled-toggle').bootstrapToggle({
|
||||
on: 'Yes',
|
||||
off: 'No',
|
||||
onstyle: 'success',
|
||||
offstyle: 'danger',
|
||||
})
|
||||
$('.schedule-enabled-toggle').each(function() {
|
||||
const enabled = JSON.parse(this.getAttribute('data-schedule-enabled'));
|
||||
$(this).bootstrapToggle(enabled ? 'on' : 'off')
|
||||
})
|
||||
$('.schedule-enabled-toggle').change(function() {
|
||||
const id = this.getAttribute('data-schedule-id');
|
||||
const enabled = this.checked;
|
||||
|
||||
fetch(`/api/v2/servers/{{data['server_id']}}/tasks/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({enabled}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
const serverId = new URLSearchParams(document.location.search).get('id')
|
||||
|
||||
$(document).ready(function () {
|
||||
@ -393,4 +420,4 @@
|
||||
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user