mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into enhancement/mobile-app
This commit is contained in:
commit
33557c026b
@ -28,7 +28,7 @@ docker-build-dev:
|
||||
docker version
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
|
||||
script:
|
||||
- |
|
||||
@ -45,6 +45,7 @@ docker-build-dev:
|
||||
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
|
||||
--build-arg "CRAFTY_VER=${VERSION}"
|
||||
--provenance false
|
||||
--tag "$CI_REGISTRY_IMAGE${tag}"
|
||||
--tag "arcadiatechnology/crafty-4${tag}"
|
||||
--platform linux/arm64/v8,linux/amd64
|
||||
@ -84,7 +85,7 @@ docker-build-prod:
|
||||
docker version
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
|
||||
script:
|
||||
- |
|
||||
@ -100,6 +101,7 @@ docker-build-prod:
|
||||
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
|
||||
--build-arg "CRAFTY_VER=${VERSION}"
|
||||
--provenance false
|
||||
--tag "$CI_REGISTRY_IMAGE:$VERSION"
|
||||
--tag "$CI_REGISTRY_IMAGE:latest"
|
||||
--tag "arcadiatechnology/crafty-4:$VERSION"
|
||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,15 +1,29 @@
|
||||
# Changelog
|
||||
## --- [4.0.22] - 2023/TBD
|
||||
## --- [4.1.0] - 2023/TBD
|
||||
### New features
|
||||
TBD
|
||||
### Refactor
|
||||
- Frontend Ajax Refactor | Start using API to send Remote Comms to Server ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/565))
|
||||
### Bug fixes
|
||||
TBD
|
||||
- Fix pipelines failing to build from gitlab pre-defined variable deprecation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/582))
|
||||
- Fix incompatible buildx provenance meta, causing digest issues on GL/DH container registries ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/582))
|
||||
- Fix Auth'd servers in roles | Refine server ordering ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/574))
|
||||
- Fix import loop detection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/575))
|
||||
### Tweaks
|
||||
TBD
|
||||
### Lang
|
||||
TBD
|
||||
<br><br>
|
||||
|
||||
## --- [4.0.22] - 2023/04/08
|
||||
### Bug fixes
|
||||
- Fix dashboard crash for users without disks or if crafty doesn't have permission to access mount point ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/571))
|
||||
- Strip Minecraft motd obfuscation chars to prevent text jumping on dashboard ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/572))
|
||||
### Tweaks
|
||||
- Improve logging on tz failures ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/569))
|
||||
- Add fallback for ping domain to provide better feedback on internet connection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/570))
|
||||
<br><br>
|
||||
|
||||
## --- [4.0.21] - 2023/03/04
|
||||
### New features
|
||||
- Add better feedback for uploads with a progress bar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546))
|
||||
|
@ -1,5 +1,5 @@
|
||||
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
|
||||
# Crafty Controller 4.0.22
|
||||
# Crafty Controller 4.1.0
|
||||
> Python based Control Panel for your Minecraft Server
|
||||
|
||||
## What is Crafty Controller?
|
||||
|
@ -253,6 +253,7 @@ class ServersController(metaclass=Singleton):
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers(user_id):
|
||||
server_ids = []
|
||||
server_data: t.List[t.Dict[str, t.Any]] = []
|
||||
user_roles = HelperUsers.user_role_query(user_id)
|
||||
for user in user_roles:
|
||||
@ -260,11 +261,13 @@ class ServersController(metaclass=Singleton):
|
||||
user.role_id
|
||||
)
|
||||
for role in role_servers:
|
||||
server_data.append(
|
||||
ServersController().get_server_instance_by_id(
|
||||
role.server_id.server_id
|
||||
if role.server_id.server_id not in server_ids:
|
||||
server_ids.append(role.server_id.server_id)
|
||||
server_data.append(
|
||||
ServersController().get_server_instance_by_id(
|
||||
role.server_id.server_id
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return server_data
|
||||
|
||||
@ -275,11 +278,10 @@ class ServersController(metaclass=Singleton):
|
||||
for role in roles_list:
|
||||
role_users = HelperUsers.get_users_from_role(role.role_id)
|
||||
for user_role in role_users:
|
||||
user_ids.add(user_role.user_id)
|
||||
user_ids.add(user_role.user_id.user_id)
|
||||
|
||||
for user_id in HelperUsers.get_super_user_list():
|
||||
user_ids.add(user_id)
|
||||
|
||||
return user_ids
|
||||
|
||||
def get_all_servers_stats(self):
|
||||
|
@ -89,6 +89,7 @@ class UsersController:
|
||||
},
|
||||
},
|
||||
"hints": {"type": "boolean"},
|
||||
"server_order": {"type": "string"},
|
||||
}
|
||||
|
||||
# **********************************************************************************
|
||||
|
@ -40,8 +40,6 @@ class Server:
|
||||
lines.append(get_code_format("underlined"))
|
||||
if "strikethrough" in e.keys():
|
||||
lines.append(get_code_format("strikethrough"))
|
||||
if "obfuscated" in e.keys():
|
||||
lines.append(get_code_format("obfuscated"))
|
||||
if "color" in e.keys():
|
||||
lines.append(get_code_format(e["color"]))
|
||||
# Then append the text
|
||||
|
@ -191,21 +191,25 @@ class Stats:
|
||||
# ENOENT, pop-up a Windows GUI error for a non-ready
|
||||
# partition or just hang.
|
||||
continue
|
||||
usage = psutil.disk_usage(part.mountpoint)
|
||||
disk_data.append(
|
||||
{
|
||||
"device": part.device,
|
||||
"total_raw": usage.total,
|
||||
"total": Helpers.human_readable_file_size(usage.total),
|
||||
"used_raw": usage.used,
|
||||
"used": Helpers.human_readable_file_size(usage.used),
|
||||
"free_raw": usage.free,
|
||||
"free": Helpers.human_readable_file_size(usage.free),
|
||||
"percent_used": usage.percent,
|
||||
"fs": part.fstype,
|
||||
"mount": part.mountpoint,
|
||||
}
|
||||
)
|
||||
try:
|
||||
usage = psutil.disk_usage(part.mountpoint)
|
||||
disk_data.append(
|
||||
{
|
||||
"device": part.device,
|
||||
"total_raw": usage.total,
|
||||
"total": Helpers.human_readable_file_size(usage.total),
|
||||
"used_raw": usage.used,
|
||||
"used": Helpers.human_readable_file_size(usage.used),
|
||||
"free_raw": usage.free,
|
||||
"free": Helpers.human_readable_file_size(usage.free),
|
||||
"percent_used": usage.percent,
|
||||
"fs": part.fstype,
|
||||
"mount": part.mountpoint,
|
||||
}
|
||||
)
|
||||
except PermissionError:
|
||||
logger.debug(f"Permission error accessing {part.mountpoint}")
|
||||
continue
|
||||
|
||||
return disk_data
|
||||
|
||||
|
@ -386,7 +386,7 @@ class HelperUsers:
|
||||
|
||||
@staticmethod
|
||||
def get_users_from_role(role_id):
|
||||
UserRoles.select().where(UserRoles.role_id == role_id).execute()
|
||||
return UserRoles.select().where(UserRoles.role_id == role_id).execute()
|
||||
|
||||
# **********************************************************************************
|
||||
# ApiKeys Methods
|
||||
|
@ -16,6 +16,7 @@ import zipfile
|
||||
import pathlib
|
||||
import ctypes
|
||||
import shutil
|
||||
import shlex
|
||||
import subprocess
|
||||
import itertools
|
||||
from datetime import datetime
|
||||
@ -147,6 +148,29 @@ class Helpers:
|
||||
logger.error(f"Unable to resolve remote bedrock download url! \n{e}")
|
||||
return False
|
||||
|
||||
def get_execution_java(self, value, execution_command):
|
||||
if self.is_os_windows():
|
||||
execution_list = shlex.split(execution_command, posix=False)
|
||||
else:
|
||||
execution_list = shlex.split(execution_command, posix=True)
|
||||
if (
|
||||
not any(value in path for path in self.find_java_installs())
|
||||
and value != "java"
|
||||
):
|
||||
return
|
||||
if value != "java":
|
||||
if self.is_os_windows():
|
||||
execution_list[0] = '"' + value + '/bin/java"'
|
||||
else:
|
||||
execution_list[0] = '"' + value + '"'
|
||||
else:
|
||||
execution_list[0] = "java"
|
||||
execution_command = ""
|
||||
for item in execution_list:
|
||||
execution_command += item + " "
|
||||
|
||||
return execution_command
|
||||
|
||||
def detect_java(self):
|
||||
if len(self.find_java_installs()) > 0:
|
||||
return True
|
||||
@ -294,7 +318,12 @@ class Helpers:
|
||||
requests.get("https://ntp.org", timeout=1)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
try:
|
||||
logger.error("ntp.org ping failed. Falling back to google")
|
||||
requests.get("https://google.com", timeout=1)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_port(server_port):
|
||||
|
@ -134,9 +134,10 @@ class ServerInstance:
|
||||
self.last_backup_failed = False
|
||||
try:
|
||||
self.tz = get_localzone()
|
||||
except ZoneInfoNotFoundError:
|
||||
except ZoneInfoNotFoundError as e:
|
||||
logger.error(
|
||||
"Could not capture time zone from system. Falling back to Europe/London"
|
||||
f" error: {e}"
|
||||
)
|
||||
self.tz = ZoneInfo("Europe/London")
|
||||
self.server_scheduler = BackgroundScheduler(timezone=str(self.tz))
|
||||
|
@ -47,9 +47,10 @@ class TasksManager:
|
||||
self.tornado: Webserver = Webserver(helper, controller, self)
|
||||
try:
|
||||
self.tz = get_localzone()
|
||||
except ZoneInfoNotFoundError:
|
||||
except ZoneInfoNotFoundError as e:
|
||||
logger.error(
|
||||
"Could not capture time zone from system. Falling back to Europe/London"
|
||||
f" error: {e}"
|
||||
)
|
||||
self.tz = "Europe/London"
|
||||
self.scheduler = BackgroundScheduler(timezone=str(self.tz))
|
||||
@ -420,6 +421,7 @@ class TasksManager:
|
||||
)
|
||||
for item in jobs:
|
||||
logger.info(f"JOB: {item}")
|
||||
return task.schedule_id
|
||||
|
||||
def remove_all_server_tasks(self, server_id):
|
||||
schedules = HelpersManagement.get_schedules_by_server(server_id)
|
||||
@ -449,7 +451,6 @@ class TasksManager:
|
||||
# created task a child of itself.
|
||||
if str(job_data.get("parent")) == str(sch_id):
|
||||
job_data["parent"] = None
|
||||
|
||||
HelpersManagement.update_scheduled_task(sch_id, job_data)
|
||||
|
||||
if not (
|
||||
|
@ -281,74 +281,7 @@ class AjaxHandler(BaseHandler):
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
|
||||
if page == "send_command":
|
||||
command = self.get_body_argument("command", default=None, strip=True)
|
||||
server_id = self.get_argument("id", None)
|
||||
|
||||
if server_id is None:
|
||||
logger.warning("Server ID not found in send_command ajax call")
|
||||
Console.warning("Server ID not found in send_command ajax call")
|
||||
|
||||
srv_obj = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
|
||||
if command == srv_obj.settings["stop_command"]:
|
||||
logger.info(
|
||||
"Stop command detected as terminal input - intercepting."
|
||||
+ f"Starting Crafty's stop process for server with id: {server_id}"
|
||||
)
|
||||
self.controller.management.send_command(
|
||||
exec_user["user_id"], server_id, self.get_remote_ip(), "stop_server"
|
||||
)
|
||||
command = None
|
||||
elif command == "restart":
|
||||
logger.info(
|
||||
"Restart command detected as terminal input - intercepting."
|
||||
+ f"Starting Crafty's stop process for server with id: {server_id}"
|
||||
)
|
||||
self.controller.management.send_command(
|
||||
exec_user["user_id"],
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
"restart_server",
|
||||
)
|
||||
command = None
|
||||
if command:
|
||||
if srv_obj.check_running():
|
||||
srv_obj.send_command(command)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Sent command to "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)} "
|
||||
f"terminal: {command}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
elif page == "send_order":
|
||||
self.controller.users.update_server_order(
|
||||
exec_user["user_id"], bleach.clean(self.get_argument("order"))
|
||||
)
|
||||
return
|
||||
|
||||
elif page == "backup_now":
|
||||
server_id = self.get_argument("id", None)
|
||||
if server_id is None:
|
||||
logger.error("Server ID is none. Canceling backup!")
|
||||
return
|
||||
|
||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
self.controller.management.add_to_audit_log_raw(
|
||||
self.controller.users.get_user_by_id(exec_user["user_id"])["username"],
|
||||
exec_user["user_id"],
|
||||
server_id,
|
||||
f"Backup now executed for server {server_id} ",
|
||||
source_ip=self.get_remote_ip(),
|
||||
)
|
||||
|
||||
server.backup_server()
|
||||
|
||||
elif page == "select_photo":
|
||||
if page == "select_photo":
|
||||
if exec_user["superuser"]:
|
||||
photo = urllib.parse.unquote(self.get_argument("photo", ""))
|
||||
opacity = self.get_argument("opacity", 100)
|
||||
@ -382,23 +315,6 @@ class AjaxHandler(BaseHandler):
|
||||
self.controller.cached_login = "login_1.jpg"
|
||||
return
|
||||
|
||||
elif page == "kill":
|
||||
if not permissions["Commands"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Commands")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
svr = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
try:
|
||||
svr.kill()
|
||||
time.sleep(5)
|
||||
svr.cleanup_server_object()
|
||||
svr.record_server_stats()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Could not find PID for requested termsig. Full error: {e}"
|
||||
)
|
||||
return
|
||||
elif page == "eula":
|
||||
server_id = self.get_argument("id", None)
|
||||
svr = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
@ -624,12 +540,6 @@ class AjaxHandler(BaseHandler):
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
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 not permissions["Backup"] in user_perms:
|
||||
@ -668,84 +578,6 @@ class AjaxHandler(BaseHandler):
|
||||
):
|
||||
os.remove(file_path)
|
||||
|
||||
elif page == "delete_server":
|
||||
if not permissions["Config"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
logger.info(
|
||||
f"Removing server from panel for server: "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)}"
|
||||
)
|
||||
|
||||
server_data = self.controller.servers.get_server_data(server_id)
|
||||
server_name = server_data["server_name"]
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, False)
|
||||
|
||||
elif page == "delete_server_files":
|
||||
if not permissions["Config"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
logger.info(
|
||||
f"Removing server and all associated files for server: "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)}"
|
||||
)
|
||||
|
||||
server_data = self.controller.servers.get_server_data(server_id)
|
||||
server_name = server_data["server_name"]
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
for server in self.controller.servers.failed_servers:
|
||||
if server["server_id"] == int(server_id):
|
||||
return
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, True)
|
||||
|
||||
elif page == "delete_unloaded_server":
|
||||
if not permissions["Config"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
logger.info(
|
||||
f"Removing server and all associated files for server: "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)}"
|
||||
)
|
||||
|
||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
||||
server_name = server_data["server_name"]
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
for item in self.controller.servers.failed_servers[:]:
|
||||
if item["server_id"] == int(server_id):
|
||||
self.controller.servers.failed_servers.remove(item)
|
||||
self.controller.remove_unloaded_server(server_id)
|
||||
|
||||
def check_server_id(self, server_id, page_name):
|
||||
if server_id is None:
|
||||
logger.warning(
|
||||
|
@ -6,7 +6,6 @@ import typing as t
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
import shlex
|
||||
import urllib.parse
|
||||
import bleach
|
||||
import requests
|
||||
@ -17,7 +16,6 @@ from tornado import iostream
|
||||
# TZLocal is set as a hidden import on win pipeline
|
||||
from tzlocal import get_localzone
|
||||
from tzlocal.utils import ZoneInfoNotFoundError
|
||||
from croniter import croniter
|
||||
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
@ -256,7 +254,12 @@ class PanelHandler(BaseHandler):
|
||||
user_order = user_order["server_order"].split(",")
|
||||
page_servers = []
|
||||
server_ids = []
|
||||
|
||||
for server in defined_servers:
|
||||
server_ids.append(str(server.server_id))
|
||||
if str(server.server_id) not in user_order:
|
||||
# a little unorthodox, but this will cut out a loop.
|
||||
# adding servers to the user order that don't already exist there.
|
||||
user_order.append(str(server.server_id))
|
||||
for server_id in user_order[:]:
|
||||
for server in defined_servers[:]:
|
||||
if str(server.server_id) == str(server_id):
|
||||
@ -265,14 +268,7 @@ class PanelHandler(BaseHandler):
|
||||
)
|
||||
user_order.remove(server_id)
|
||||
defined_servers.remove(server)
|
||||
|
||||
for server in defined_servers:
|
||||
server_ids.append(str(server.server_id))
|
||||
if server not in page_servers:
|
||||
page_servers.append(
|
||||
DatabaseShortcuts.get_data_obj(server.server_object)
|
||||
)
|
||||
|
||||
break
|
||||
for server_id in user_order[:]:
|
||||
# remove IDs in list that user no longer has access to
|
||||
if str(server_id) not in server_ids:
|
||||
@ -452,6 +448,7 @@ class PanelHandler(BaseHandler):
|
||||
page_servers.append(server)
|
||||
un_used_servers.remove(server)
|
||||
user_order.remove(server_id)
|
||||
break
|
||||
# we only want to set these server stats values once.
|
||||
# We need to update the flag so it only hits that if once.
|
||||
flag += 1
|
||||
@ -1053,7 +1050,7 @@ class PanelHandler(BaseHandler):
|
||||
page_data["schedule"]["cron_string"] = ""
|
||||
page_data["schedule"]["delay"] = 0
|
||||
page_data["schedule"]["time"] = ""
|
||||
page_data["schedule"]["interval"] = ""
|
||||
page_data["schedule"]["interval"] = 1
|
||||
# we don't need to check difficulty here.
|
||||
# We'll just default to basic for new schedules
|
||||
page_data["schedule"]["difficulty"] = "basic"
|
||||
@ -1556,156 +1553,6 @@ class PanelHandler(BaseHandler):
|
||||
role = self.controller.roles.get_role(r)
|
||||
exec_user_role.add(role["role_name"])
|
||||
|
||||
if page == "server_detail":
|
||||
if not permissions[
|
||||
"Config"
|
||||
] in self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
):
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_name = self.get_argument("server_name", None)
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
shutdown_timeout = self.get_argument("shutdown_timeout", 60)
|
||||
if superuser:
|
||||
log_path = self.get_argument("log_path", "")
|
||||
if log_path:
|
||||
if Helpers.is_os_windows():
|
||||
log_path.replace(" ", "^ ")
|
||||
log_path = Helpers.wtol_path(log_path)
|
||||
if not self.helper.validate_traversal(server_obj.path, log_path):
|
||||
log_path = ""
|
||||
executable = self.get_argument("executable", None)
|
||||
execution_command = self.get_argument("execution_command", None)
|
||||
server_ip = self.get_argument("server_ip", None)
|
||||
server_port = self.get_argument("server_port", None)
|
||||
if int(server_port) < 1 or int(server_port) > 65535:
|
||||
self.redirect(
|
||||
"/panel/error?error=Constraint Error: "
|
||||
"Port must be greater than 0 and less than 65535"
|
||||
)
|
||||
return
|
||||
executable_update_url = self.get_argument("executable_update_url", "")
|
||||
show_status = int(float(self.get_argument("show_status", "0")))
|
||||
else:
|
||||
execution_command = server_obj.execution_command
|
||||
executable = server_obj.executable
|
||||
stop_command = self.get_argument("stop_command", None)
|
||||
auto_start_delay = self.get_argument("auto_start_delay", "10")
|
||||
auto_start = int(float(self.get_argument("auto_start", "0")))
|
||||
crash_detection = int(float(self.get_argument("crash_detection", "0")))
|
||||
logs_delete_after = int(float(self.get_argument("logs_delete_after", "0")))
|
||||
java_selection = self.get_argument("java_selection", None)
|
||||
# make sure there is no whitespace
|
||||
ignored_exits = self.get_argument("ignored_exits", "").replace(" ", "")
|
||||
# subpage = self.get_argument('subpage', None)
|
||||
|
||||
server_id = self.check_server_id()
|
||||
if server_id is None:
|
||||
return
|
||||
if java_selection:
|
||||
try:
|
||||
if self.helper.is_os_windows():
|
||||
execution_list = shlex.split(execution_command, posix=False)
|
||||
else:
|
||||
execution_list = shlex.split(execution_command, posix=True)
|
||||
except ValueError:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid execution command. Java path"
|
||||
" must be surrounded by quotes."
|
||||
" (Are you missing a closing quote?)"
|
||||
)
|
||||
if (
|
||||
not any(
|
||||
java_selection in path for path in Helpers.find_java_installs()
|
||||
)
|
||||
and java_selection != "java"
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Attack attempted."
|
||||
+ " A copy of this report is being sent to server owner."
|
||||
)
|
||||
self.controller.management.add_to_audit_log_raw(
|
||||
exec_user["username"],
|
||||
exec_user["user_id"],
|
||||
server_id,
|
||||
f"Attempted to send bad java path for {server_id}."
|
||||
+ " Possible attack. Act accordingly.",
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
return
|
||||
if java_selection != "java":
|
||||
if self.helper.is_os_windows():
|
||||
execution_list[0] = '"' + java_selection + '/bin/java"'
|
||||
else:
|
||||
execution_list[0] = '"' + java_selection + '"'
|
||||
else:
|
||||
execution_list[0] = "java"
|
||||
execution_command = ""
|
||||
for item in execution_list:
|
||||
execution_command += item + " "
|
||||
|
||||
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
|
||||
stale_executable = server_obj.executable
|
||||
# Compares old jar name to page data being passed.
|
||||
# If they are different we replace the executable name in the
|
||||
if str(stale_executable) != str(executable):
|
||||
execution_command = execution_command.replace(
|
||||
str(stale_executable), str(executable)
|
||||
)
|
||||
|
||||
server_obj.server_name = server_name
|
||||
server_obj.shutdown_timeout = shutdown_timeout
|
||||
if superuser:
|
||||
if Helpers.validate_traversal(
|
||||
self.helper.get_servers_root_dir(), server_obj.path
|
||||
):
|
||||
server_obj.log_path = log_path
|
||||
if Helpers.validate_traversal(
|
||||
self.helper.get_servers_root_dir(), executable
|
||||
):
|
||||
server_obj.executable = executable
|
||||
server_obj.execution_command = execution_command
|
||||
server_obj.server_ip = server_ip
|
||||
server_obj.server_port = server_port
|
||||
server_obj.executable_update_url = executable_update_url
|
||||
server_obj.show_status = show_status
|
||||
else:
|
||||
server_obj.log_path = server_obj.log_path
|
||||
server_obj.executable = server_obj.executable
|
||||
server_obj.execution_command = execution_command
|
||||
server_obj.server_ip = server_obj.server_ip
|
||||
server_obj.server_port = server_obj.server_port
|
||||
server_obj.executable_update_url = server_obj.executable_update_url
|
||||
server_obj.stop_command = stop_command
|
||||
server_obj.auto_start_delay = auto_start_delay
|
||||
server_obj.auto_start = auto_start
|
||||
server_obj.crash_detection = crash_detection
|
||||
server_obj.logs_delete_after = logs_delete_after
|
||||
server_obj.ignored_exits = ignored_exits
|
||||
failed = False
|
||||
for servers in self.controller.servers.failed_servers:
|
||||
if servers["server_id"] == int(server_id):
|
||||
failed = True
|
||||
if not failed:
|
||||
self.controller.servers.update_server(server_obj)
|
||||
else:
|
||||
self.controller.servers.update_unloaded_server(server_obj)
|
||||
self.controller.servers.init_all_servers()
|
||||
self.controller.servers.crash_detection(server_obj)
|
||||
|
||||
self.controller.servers.refresh_server_settings(server_id)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Edited server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=config")
|
||||
|
||||
if page == "server_backup":
|
||||
logger.debug(self.request.arguments)
|
||||
|
||||
@ -1802,336 +1649,6 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
self.redirect("/panel/config_json")
|
||||
|
||||
if page == "new_schedule":
|
||||
server_id = self.check_server_id()
|
||||
if not server_id:
|
||||
return
|
||||
|
||||
if (
|
||||
not permissions["Schedule"]
|
||||
in self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
and not superuser
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Unauthorized access: User not authorized"
|
||||
)
|
||||
return
|
||||
|
||||
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"))
|
||||
name = bleach.clean(self.get_argument("name", ""))
|
||||
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":
|
||||
sch_time = bleach.clean(self.get_argument("time", None))
|
||||
if int(interval) > 30:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid argument."
|
||||
" Days must be 30 or fewer."
|
||||
)
|
||||
return
|
||||
if action == "command":
|
||||
command = 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"
|
||||
|
||||
elif difficulty == "reaction":
|
||||
interval_type = "reaction"
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
delay = bleach.clean(self.get_argument("delay", None))
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
if action == "command":
|
||||
command = 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", ""))
|
||||
if not croniter.is_valid(cron_string):
|
||||
self.redirect(
|
||||
"/panel/error?error=INVALID FORMAT: Invalid Cron Format."
|
||||
)
|
||||
return
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
if action == "command":
|
||||
command = 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", "0")) == "1":
|
||||
enabled = True
|
||||
else:
|
||||
enabled = False
|
||||
if bleach.clean(self.get_argument("one_time", "0")) == "1":
|
||||
one_time = True
|
||||
else:
|
||||
one_time = False
|
||||
|
||||
if interval_type == "days":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"start_time": sch_time,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
elif difficulty == "reaction":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": "",
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"parent": parent,
|
||||
"delay": delay,
|
||||
}
|
||||
elif difficulty == "advanced":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": "",
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": cron_string,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
else:
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"enabled": enabled,
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "00:00",
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
|
||||
self.tasks_manager.schedule_job(job_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Edited server {server_id}: added scheduled job",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
|
||||
|
||||
if page == "edit_schedule":
|
||||
server_id = self.check_server_id()
|
||||
if not server_id:
|
||||
return
|
||||
|
||||
if (
|
||||
not permissions["Schedule"]
|
||||
in self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
and not superuser
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Unauthorized access: User not authorized"
|
||||
)
|
||||
return
|
||||
|
||||
sch_id = self.get_argument("sch_id", None)
|
||||
if sch_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Schedule ID")
|
||||
|
||||
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"))
|
||||
name = bleach.clean(self.get_argument("name", ""))
|
||||
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":
|
||||
sch_time = bleach.clean(self.get_argument("time", None))
|
||||
if int(interval) > 30:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid argument."
|
||||
" Days must be 30 or fewer."
|
||||
)
|
||||
return
|
||||
if action == "command":
|
||||
command = 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"
|
||||
elif difficulty == "reaction":
|
||||
interval_type = "reaction"
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
delay = bleach.clean(self.get_argument("delay", None))
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
if action == "command":
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
else:
|
||||
interval_type = ""
|
||||
cron_string = bleach.clean(self.get_argument("cron", ""))
|
||||
if not croniter.is_valid(cron_string):
|
||||
self.redirect(
|
||||
"/panel/error?error=INVALID FORMAT: Invalid Cron Format."
|
||||
)
|
||||
return
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
if action == "command":
|
||||
command = 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", "0")) == "1":
|
||||
enabled = True
|
||||
else:
|
||||
enabled = False
|
||||
if bleach.clean(self.get_argument("one_time", "0")) == "1":
|
||||
one_time = True
|
||||
else:
|
||||
one_time = False
|
||||
|
||||
if interval_type == "days":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"start_time": sch_time,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
elif difficulty == "advanced":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": "",
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": cron_string,
|
||||
"delay": "",
|
||||
"parent": "",
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
}
|
||||
elif difficulty == "reaction":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": "",
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"parent": parent,
|
||||
"delay": delay,
|
||||
}
|
||||
else:
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"enabled": enabled,
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "00:00",
|
||||
"delay": "",
|
||||
"parent": "",
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
}
|
||||
self.tasks_manager.update_job(sch_id, job_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Edited server {server_id}: updated schedule",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
|
||||
|
||||
elif page == "edit_user":
|
||||
if bleach.clean(self.get_argument("username", None)).lower() == "system":
|
||||
self.redirect(
|
||||
|
@ -4,6 +4,36 @@ from peewee import DoesNotExist
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
modify_role_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
},
|
||||
"servers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_id": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string",
|
||||
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
|
||||
},
|
||||
},
|
||||
"required": ["server_id", "permissions"],
|
||||
},
|
||||
},
|
||||
"manager": {"type": ["integer", "null"]},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
}
|
||||
|
||||
basic_modify_role_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
@ -109,7 +139,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
|
||||
)
|
||||
|
||||
try:
|
||||
validate(data, modify_role_schema)
|
||||
if auth_data[4]["superuser"]:
|
||||
validate(data, modify_role_schema)
|
||||
else:
|
||||
validate(data, basic_modify_role_schema)
|
||||
except ValidationError as e:
|
||||
return self.finish_json(
|
||||
400,
|
||||
|
@ -13,20 +13,39 @@ server_patch_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_name": {"type": "string", "minLength": 1},
|
||||
"path": {"type": "string", "minLength": 1},
|
||||
"backup_path": {"type": "string"},
|
||||
"executable": {"type": "string"},
|
||||
"log_path": {"type": "string", "minLength": 1},
|
||||
"execution_command": {"type": "string", "minLength": 1},
|
||||
"java_selection": {"type": "string"},
|
||||
"auto_start": {"type": "boolean"},
|
||||
"auto_start_delay": {"type": "integer"},
|
||||
"auto_start_delay": {"type": "integer", "minimum": 0},
|
||||
"crash_detection": {"type": "boolean"},
|
||||
"stop_command": {"type": "string"},
|
||||
"executable_update_url": {"type": "string", "minLength": 1},
|
||||
"executable_update_url": {"type": "string"},
|
||||
"server_ip": {"type": "string", "minLength": 1},
|
||||
"server_port": {"type": "integer"},
|
||||
"logs_delete_after": {"type": "integer"},
|
||||
"type": {"type": "string", "minLength": 1},
|
||||
"shutdown_timeout": {"type": "integer", "minimum": 0},
|
||||
"logs_delete_after": {"type": "integer", "minimum": 0},
|
||||
"ignored_exits": {"type": "string"},
|
||||
"show_status": {"type": "boolean"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
}
|
||||
basic_server_patch_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_name": {"type": "string", "minLength": 1},
|
||||
"executable": {"type": "string"},
|
||||
"java_selection": {"type": "string"},
|
||||
"auto_start": {"type": "boolean"},
|
||||
"auto_start_delay": {"type": "integer", "minimum": 0},
|
||||
"crash_detection": {"type": "boolean"},
|
||||
"stop_command": {"type": "string"},
|
||||
"shutdown_timeout": {"type": "integer"},
|
||||
"logs_delete_after": {"type": "integer", "minimum": 0},
|
||||
"ignored_exits": {"type": "string"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
@ -63,7 +82,11 @@ class ApiServersServerIndexHandler(BaseApiHandler):
|
||||
)
|
||||
|
||||
try:
|
||||
validate(data, server_patch_schema)
|
||||
# prevent general users from becoming bad actors
|
||||
if auth_data[4]["superuser"]:
|
||||
validate(data, server_patch_schema)
|
||||
else:
|
||||
validate(data, basic_server_patch_schema)
|
||||
except ValidationError as e:
|
||||
return self.finish_json(
|
||||
400,
|
||||
@ -88,9 +111,24 @@ class ApiServersServerIndexHandler(BaseApiHandler):
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
java_flag = False
|
||||
for key in data:
|
||||
# If we don't validate the input there could be security issues
|
||||
if key == "java_selection" and data[key] != "none":
|
||||
try:
|
||||
command = self.helper.get_execution_java(
|
||||
data[key], server_obj.execution_command
|
||||
)
|
||||
setattr(server_obj, "execution_command", command)
|
||||
except ValueError:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID EXECUTION COMMAND"}
|
||||
)
|
||||
java_flag = True
|
||||
|
||||
if key != "path":
|
||||
if key == "execution_command" and java_flag:
|
||||
continue
|
||||
setattr(server_obj, key, data[key])
|
||||
self.controller.servers.update_server(server_obj)
|
||||
|
||||
@ -134,7 +172,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
|
||||
)
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, remove_files)
|
||||
failed = False
|
||||
for item in self.controller.servers.failed_servers[:]:
|
||||
if item["server_id"] == int(server_id):
|
||||
self.controller.servers.failed_servers.remove(item)
|
||||
failed = True
|
||||
|
||||
if failed:
|
||||
self.controller.remove_unloaded_server(server_id)
|
||||
else:
|
||||
self.controller.remove_server(server_id, remove_files)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
|
@ -35,7 +35,13 @@ class ApiServersServerStdinHandler(BaseApiHandler):
|
||||
"Please report this to the devs"
|
||||
)
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
decoded = self.request.body.decode("utf-8")
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
f"Sent command ({decoded}) to terminal",
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip(),
|
||||
)
|
||||
if svr.send_command(self.request.body.decode("utf-8")):
|
||||
return self.finish_json(
|
||||
200,
|
||||
|
@ -1,16 +1,121 @@
|
||||
# TODO: create and read
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from croniter import croniter
|
||||
from jsonschema import ValidationError, validate
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
new_task_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"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 ApiServersServerTasksIndexHandler(BaseApiHandler):
|
||||
def get(self, server_id: str, task_id: str):
|
||||
pass
|
||||
|
||||
def post(self, server_id: str, task_id: str):
|
||||
pass
|
||||
def post(self, server_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, new_task_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"})
|
||||
data["server_id"] = server_id
|
||||
if not data.get("start_time"):
|
||||
data["start_time"] = "00:00"
|
||||
|
||||
# validate cron string
|
||||
if data["cron_string"] != "" and not croniter.is_valid(data["cron_string"]):
|
||||
return self.finish_json(
|
||||
405,
|
||||
{
|
||||
"status": "error",
|
||||
"error": self.helper.translation.translate(
|
||||
"error",
|
||||
"cronFormat",
|
||||
self.controller.users.get_user_lang_by_id(
|
||||
auth_data[4]["user_id"]
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
if "parent" not in data:
|
||||
data["parent"] = None
|
||||
task_id = self.tasks_manager.schedule_job(data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
f"Edited server {server_id}: added schedule",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
|
||||
self.finish_json(200, {"status": "ok", "data": {"schedule_id": task_id}})
|
||||
|
@ -3,6 +3,7 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from croniter import croniter
|
||||
from jsonschema import ValidationError, validate
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
|
||||
@ -35,6 +36,7 @@ task_patch_schema = {
|
||||
"",
|
||||
],
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
|
||||
"command": {"type": ["string", "null"]},
|
||||
"one_time": {"type": "boolean", "default": False},
|
||||
@ -49,10 +51,47 @@ task_patch_schema = {
|
||||
|
||||
class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
|
||||
def get(self, server_id: str, task_id: str):
|
||||
pass
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
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"})
|
||||
self.finish_json(200, self.controller.management.get_scheduled_task(task_id))
|
||||
|
||||
def delete(self, server_id: str, task_id: str):
|
||||
pass
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
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"})
|
||||
|
||||
try:
|
||||
self.tasks_manager.remove_job(task_id)
|
||||
except Exception:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "NO SCHEDULE FOUND"}
|
||||
)
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
f"Edited server {server_id}: removed schedule",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
|
||||
return self.finish_json(200, {"status": "ok"})
|
||||
|
||||
def patch(self, server_id: str, task_id: str):
|
||||
auth_data = self.authenticate_user()
|
||||
@ -96,6 +135,21 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
|
||||
if str(data.get("parent")) == str(task_id) and data.get("parent") is not None:
|
||||
data["parent"] = None
|
||||
|
||||
data["server_id"] = server_id
|
||||
if data["cron_string"] != "" and not croniter.is_valid(data["cron_string"]):
|
||||
return self.finish_json(
|
||||
405,
|
||||
{
|
||||
"status": "error",
|
||||
"error": self.helper.translation.translate(
|
||||
"error",
|
||||
"cronFormat",
|
||||
self.controller.users.get_user_lang_by_id(
|
||||
auth_data[4]["user_id"]
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
self.tasks_manager.update_job(task_id, data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
|
@ -333,7 +333,7 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
if import_type == "import_jar":
|
||||
if self.helper.is_subdir(
|
||||
import_server_path, self.controller.project_root
|
||||
self.controller.project_root, import_server_path
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Loop Error: The selected path will cause"
|
||||
@ -499,7 +499,7 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
if import_type == "import_jar":
|
||||
if self.helper.is_subdir(
|
||||
import_server_path, self.controller.project_root
|
||||
self.controller.project_root, import_server_path
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Loop Error: The selected path will cause"
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": 22
|
||||
"minor": 1,
|
||||
"sub": 0
|
||||
}
|
||||
|
@ -100,6 +100,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if len(data['hosts_data']['disk_json']) > 0 %}
|
||||
<div class="col-12 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper" style="width: 100%;">
|
||||
@ -107,7 +108,7 @@
|
||||
</h5>
|
||||
<div id="storage_data">
|
||||
<div class="row">
|
||||
{% for item in data.get('hosts_data').get('disk_json') %}
|
||||
{% for item in data['hosts_data']['disk_json'] %}
|
||||
{% if item["mount"] in data["monitored"] %}
|
||||
<div id="{{item['device']}}" class="col-xl-3 col-lg-3 col-md-4 col-12">
|
||||
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading"
|
||||
@ -137,6 +138,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -645,10 +647,13 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
url: `/api/v2/servers/${server_id}/action/${command}`,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
if (command === "clone_server" && data.status === "ok") {
|
||||
window.location.reload();
|
||||
}
|
||||
/*setTimeout(function () {
|
||||
if (command != 'start_server') {
|
||||
location.reload();
|
||||
@ -703,24 +708,6 @@
|
||||
document.querySelector('.dynamicMsg').appendChild(parentEl);
|
||||
}
|
||||
|
||||
function send_kill(server_id) {
|
||||
/* this getCookie function is in base.html */
|
||||
const token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/kill?id=' + server_id,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
/*setTimeout(function () {
|
||||
location.reload();
|
||||
}, 10000);*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function update_one_server_status(server) {
|
||||
/* Mobile view update */
|
||||
server_cpu = document.getElementById('server_cpu_' + server.id);
|
||||
@ -899,17 +886,11 @@
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
send_kill(server_id);
|
||||
send_command(server_id, "kill_server");
|
||||
let dialog = bootbox.dialog({
|
||||
title: '{% raw translate("dashboard", "killing", data["lang"]) %}',
|
||||
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
|
||||
});
|
||||
|
||||
dialog.init(function () {
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 15000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -931,8 +912,8 @@
|
||||
var storage_html = '<div class="row">';
|
||||
for (i = 0; i < hostStats.disk_usage.length; i++) {
|
||||
if (hostStats.mounts.includes(hostStats.disk_usage[i].mount)) {
|
||||
storage_html += `<div id="{{item['device']}}" class="col-xl-3 col-lg-3 col-md-4 col-12">
|
||||
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading" id="title_{{item['device']}}" data-toggle="tooltip" data-placement="bottom" title="${hostStats.disk_usage[i].mount}" style="max-width: 100%;"><i class="fas fa-hdd"></i> ${hostStats.disk_usage[i].mount}</h4>
|
||||
storage_html += `<div id="host_storage" class="col-xl-3 col-lg-3 col-md-4 col-12">
|
||||
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading" id="title_host_storage" data-toggle="tooltip" data-placement="bottom" title="${hostStats.disk_usage[i].mount}" style="max-width: 100%;"><i class="fas fa-hdd"></i> ${hostStats.disk_usage[i].mount}</h4>
|
||||
<div class="progress" style="display: inline-block; height: 20px; width: 100%; background-color: rgb(139, 139, 139) !important;">
|
||||
<div class="progress-bar`;
|
||||
if (hostStats.disk_usage[i].percent_used <= 58) {
|
||||
@ -998,7 +979,13 @@
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
cloneServer(server_id);
|
||||
send_command(server_id, 'clone_server');
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -1006,16 +993,6 @@
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function cloneServer(server_id) {
|
||||
send_command(server_id, 'clone_server');
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script src="/static/assets/vendors/js/jquery-ui.js"></script>
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/jquery-ui.css">
|
||||
@ -1067,12 +1044,12 @@
|
||||
const token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
type: "PATCH",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/send_order?order=' + id_string,
|
||||
data: {
|
||||
order: id_string,
|
||||
},
|
||||
url: `/api/v2/users/@me`,
|
||||
data: JSON.stringify({
|
||||
server_order: id_string,
|
||||
}),
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
|
@ -321,9 +321,60 @@
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
function gather_server_json() {
|
||||
servers = [];
|
||||
for (s = 0; s < page_servers.length; s++){
|
||||
mask = ""
|
||||
for (i = 0; i < permissions.length; i++){
|
||||
if ($(`#permission_${page_servers[s].id}_${permissions[i]}`).prop('checked')){
|
||||
mask += "1"
|
||||
}else{
|
||||
mask += "0"
|
||||
}
|
||||
}
|
||||
servers.push(JSON.stringify({"id": page_servers[s].id, "permissions": mask}));
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
});
|
||||
const roleId = new URLSearchParams(document.location.search).get('id');
|
||||
|
||||
$("#config_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let configForm = document.getElementById("config_form");
|
||||
|
||||
let formData = new FormData(configForm);
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
let send_object = Object()
|
||||
send_object.servers = []
|
||||
send_object.name = formDataObject.role_name
|
||||
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
|
||||
let res = await fetch(`/api/v2/roles/${roleId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.reload();
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.error,
|
||||
message: responseData.error_data
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
@ -14,7 +14,8 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<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 />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -76,10 +77,14 @@
|
||||
<li class="playerItem">
|
||||
<h3>{{ player }}</h3>
|
||||
<div class="buttons">
|
||||
<button onclick="send_command_to_server('ban {{ player }}')" type="button" class="btn btn-danger">Ban</button>
|
||||
<button onclick="send_command_to_server('kick {{ player }}')" type="button" class="btn btn-outline-danger">Kick</button>
|
||||
<button onclick="send_command_to_server('op {{ player }}')" type="button" class="btn btn-warning">OP</button>
|
||||
<button onclick="send_command_to_server('deop {{ player }}')" type="button" class="btn btn-outline-warning">De-OP</button>
|
||||
<button onclick="send_command_to_server('ban {{ player }}')" type="button"
|
||||
class="btn btn-danger">Ban</button>
|
||||
<button onclick="send_command_to_server('kick {{ player }}')" type="button"
|
||||
class="btn btn-outline-danger">Kick</button>
|
||||
<button onclick="send_command_to_server('op {{ player }}')" type="button"
|
||||
class="btn btn-warning">OP</button>
|
||||
<button onclick="send_command_to_server('deop {{ player }}')" type="button"
|
||||
class="btn btn-outline-warning">De-OP</button>
|
||||
</div>
|
||||
</li>
|
||||
{% end %}
|
||||
@ -136,21 +141,22 @@
|
||||
|
||||
});
|
||||
|
||||
function send_command_to_server(command) {
|
||||
async function send_command_to_server(command) {
|
||||
console.log(command)
|
||||
var token = getCookie("_xsrf")
|
||||
console.log('sending command: ' + command)
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/send_command?id=' + serverId,
|
||||
data: { command },
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: command,
|
||||
});
|
||||
|
||||
let responseData = await res.text();
|
||||
console.log("got response:");
|
||||
console.log(responseData);
|
||||
}
|
||||
|
||||
|
||||
|
@ -326,7 +326,7 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/backup_now?id=' + server_id,
|
||||
url: `/api/v2/servers/${server_id}/action/backup_server`,
|
||||
success: function (data) {
|
||||
return;
|
||||
},
|
||||
|
@ -43,10 +43,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<form class="forms-sample" method="post" id="config_form" action="/panel/server_detail">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
<form class="forms-sample" method="post" id="config_form">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small
|
||||
@ -96,7 +93,7 @@
|
||||
</label>
|
||||
<select class="form-select form-control form-control-lg select-css" id="java_selection"
|
||||
name="java_selection" form="config_form">
|
||||
<option value="">{{ translate('serverConfig',
|
||||
<option value="none">{{ translate('serverConfig',
|
||||
'javaNoChange', data['lang'])}}</option>
|
||||
{% for path in data['java_versions'] %}
|
||||
<option value="{{path}}">{{path}}</option>
|
||||
@ -359,7 +356,7 @@
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_server?id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}`,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
@ -373,7 +370,7 @@
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_server_files?id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}?files=true`,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
@ -393,7 +390,7 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}/action/${command}`,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
@ -522,7 +519,7 @@
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_unloaded_server?id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}`,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
@ -550,11 +547,92 @@
|
||||
$('.port-hint').popover("hide");
|
||||
});
|
||||
|
||||
async function postFormFieldsAsJson({ url, formData }) {
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject);
|
||||
|
||||
//Set the fetch options (headers, body)
|
||||
let fetchOptions = {
|
||||
//HTTP method set to POST.
|
||||
method: "PATCH",
|
||||
//Set the headers that specify you're sending a JSON body request and accepting JSON response
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
// POST request body as JSON string.
|
||||
body: formDataJsonString,
|
||||
};
|
||||
|
||||
//Get the response body as JSON.
|
||||
//If the response was not OK, throw an error.
|
||||
let res = await fetch(url, fetchOptions);
|
||||
|
||||
//If the response is not ok throw an error (for debugging)
|
||||
if (!res.ok) {
|
||||
let error = await res.text();
|
||||
throw new Error(error);
|
||||
}
|
||||
//If the response was OK, return the response body.
|
||||
return res.json();
|
||||
}
|
||||
function replacer(key, value) {
|
||||
if (key != "ignored_exits") {
|
||||
if (typeof value == "boolean" || key === "executable_update_url") {
|
||||
return value
|
||||
} else {
|
||||
return (isNaN(value) ? value : +value);
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
let token = getCookie("_xsrf")
|
||||
webSocket.on('remove_spinner', function () {
|
||||
document.getElementById("update-spinner").style.visibility = "hidden";
|
||||
});
|
||||
$("#config_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let configForm = document.getElementById("config_form");
|
||||
|
||||
let formData = new FormData(configForm);
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
//We need to make sure these are sent regardless of whether or not they're checked
|
||||
formDataObject.show_status = $("#show_status").prop('checked');
|
||||
formDataObject.crash_detection = $("#crash_detection").prop('checked');
|
||||
formDataObject.auto_start = $("#auto_start").prop('checked');
|
||||
console.log(formDataObject);
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
formDataJsonString["ignored_exits"] = toString(formDataJsonString["ignored_exits"]);
|
||||
console.log(formDataJsonString.ignored_exits)
|
||||
|
||||
console.log(formDataJsonString);
|
||||
|
||||
let res = await fetch(`/api/v2/servers/${serverId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.reload();
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.error,
|
||||
message: responseData.error_data
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -37,15 +37,12 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-sm-8">
|
||||
{% if data['new_schedule'] == True %}
|
||||
<form class="forms-sample" method="post"
|
||||
<form class="forms-sample" method="post" id="new_schedule_form"
|
||||
action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
|
||||
{% else %}
|
||||
<form class="forms-sample" method="post"
|
||||
<form class="forms-sample" method="post" id="schedule_form"
|
||||
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="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label>
|
||||
@ -89,7 +86,7 @@
|
||||
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' ,
|
||||
data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="interval" id="interval"
|
||||
value="{{ data['schedule']['interval'] }}" placeholder="Interval" required>
|
||||
value="{{ data['schedule']['interval'] }}" placeholder="Interval" required min="1">
|
||||
<br>
|
||||
<br>
|
||||
<select id="interval_type" onchange="ifDays(this);" name="interval_type"
|
||||
@ -108,7 +105,7 @@
|
||||
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' ,
|
||||
data['lang']) }}</small> </label>
|
||||
<input type="time" class="form-control" name="time" id="time"
|
||||
<input type="time" class="form-control" name="start_time" id="time"
|
||||
value="{{ data['schedule']['time'] }}" placeholder="Time" required>
|
||||
</div>
|
||||
</div>
|
||||
@ -127,7 +124,7 @@
|
||||
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="input" class="form-control" name="cron" id="cron"
|
||||
<input type="input" class="form-control" name="cron_string" id="cron"
|
||||
value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
|
||||
</div>
|
||||
</div>
|
||||
@ -234,8 +231,120 @@
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
function replacer(key, value) {
|
||||
if (key != "start_time" && key != "cron_string" && key != "interval_type") {
|
||||
if (typeof value == "boolean") {
|
||||
return value
|
||||
}
|
||||
console.log(key)
|
||||
if (key === "interval" && value === ""){
|
||||
return 0;
|
||||
}
|
||||
if (key === "command" && typeof(value === "integer")){
|
||||
return value.toString();
|
||||
}else {
|
||||
return (isNaN(value) ? value : +value);
|
||||
}
|
||||
} else {
|
||||
if (value === "" && key == "start_time"){
|
||||
return "00:00";
|
||||
}else{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverId = new URLSearchParams(document.location.search).get('id');
|
||||
const schId = new URLSearchParams(document.location.search).get('sch_id');
|
||||
$(document).ready(function () {
|
||||
console.log("ready!");
|
||||
$("#new_schedule_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let schForm = document.getElementById("new_schedule_form");
|
||||
|
||||
let formData = new FormData(schForm);
|
||||
formData.delete("difficulty");
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
//We need to make sure these are sent regardless of whether or not they're checked
|
||||
formDataObject.enabled = $("#enabled").prop('checked');
|
||||
formDataObject.one_time = $("#one_time").prop('checked');
|
||||
if ($("#difficulty").val() == "reaction"){
|
||||
formDataObject.interval_type = "reaction";
|
||||
}
|
||||
if (formDataObject.cron_string != ""){
|
||||
formDataObject.interval_type = '';
|
||||
}
|
||||
console.log(formDataObject);
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
|
||||
console.log(formDataJsonString);
|
||||
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/tasks/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.href = `/panel/server_detail?id=${serverId}&subpage=schedules`;
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.status,
|
||||
message: responseData.error
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#schedule_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let schForm = document.getElementById("schedule_form");
|
||||
|
||||
let formData = new FormData(schForm);
|
||||
formData.delete("difficulty");
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
//We need to make sure these are sent regardless of whether or not they're checked
|
||||
formDataObject.enabled = $("#enabled").prop('checked');
|
||||
formDataObject.one_time = $("#one_time").prop('checked');
|
||||
if ($("#difficulty").val() == "reaction"){
|
||||
formDataObject.interval_type = "reaction";
|
||||
}
|
||||
if (formDataObject.cron_string != ""){
|
||||
formDataObject.interval_type = '';
|
||||
}
|
||||
console.log(formDataObject);
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
|
||||
console.log(formDataJsonString);
|
||||
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/tasks/${schId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-XSRFToken': token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.href = `/panel/server_detail?id=${serverId}&subpage=schedules`;
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.error,
|
||||
message: responseData.error_data
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -265,6 +374,7 @@
|
||||
document.getElementById("parent").required = true;
|
||||
document.getElementById("interval").required = false;
|
||||
document.getElementById("time").required = false;
|
||||
$("#cron").val("");
|
||||
}
|
||||
else {
|
||||
document.getElementById("ifAdvanced").style.display = "none";
|
||||
@ -274,6 +384,7 @@
|
||||
document.getElementById("parent").required = false;
|
||||
document.getElementById("interval").required = true;
|
||||
document.getElementById("time").required = true;
|
||||
$("#cron").val("");
|
||||
}
|
||||
}
|
||||
function ifDays() {
|
||||
@ -286,22 +397,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -90,7 +90,7 @@
|
||||
<p>{{schedule.command}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.interval}}" class="action">
|
||||
{% if schedule.interval != '' %}
|
||||
{% if schedule.interval_type != '' and schedule.interval_type != 'reaction' %}
|
||||
<p>{{ translate('serverSchedules', 'every', data['lang']) }}</p>
|
||||
<p>{{schedule.interval}} {{schedule.interval_type}}</p>
|
||||
{% elif schedule.interval_type == 'reaction' %}
|
||||
@ -440,21 +440,19 @@
|
||||
});
|
||||
});
|
||||
|
||||
function del_task(sch_id, id) {
|
||||
async 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();
|
||||
let res = await fetch(`/api/v2/servers/${id}/tasks/${sch_id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'token': token,
|
||||
},
|
||||
});
|
||||
let responseData = await res;
|
||||
if (responseData.statusText === "OK") {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -179,7 +179,7 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}/action/${command}`,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
@ -311,12 +311,12 @@
|
||||
formdata.append('command', serverCommand)
|
||||
|
||||
console.log('sending command: ' + serverCommand)
|
||||
let res = await fetch("/ajax/send_command?id=" + serverId, {
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: formdata,
|
||||
body: serverCommand,
|
||||
});
|
||||
|
||||
let responseData = await res.text();
|
||||
|
@ -186,7 +186,8 @@
|
||||
"terribleFailure": "What a Terrible Failure!",
|
||||
"superError": "You must be a super user to complete this action.",
|
||||
"fileError": "File type must be an image.",
|
||||
"migration": "Crafty's main server storage is being mirgated to a new location. All server starts have been suspended during this time. Please wait while we finish this migration"
|
||||
"migration": "Crafty's main server storage is being mirgated to a new location. All server starts have been suspended during this time. Please wait while we finish this migration",
|
||||
"cronFormat": "Invalid Cron format detected"
|
||||
},
|
||||
"footer": {
|
||||
"allRightsReserved": "All rights reserved",
|
||||
@ -618,4 +619,4 @@
|
||||
"manager": "Manager",
|
||||
"selectManager": "Select Manager for User"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user