Merge branch 'dev' into tweak/improve-display

This commit is contained in:
Silversthorn 2023-05-18 09:53:23 +02:00
commit e90d6e8091
51 changed files with 1200 additions and 953 deletions

View File

@ -28,7 +28,7 @@ docker-build-dev:
docker version docker version
- docker run --rm --privileged aptman/qus -- -r - docker run --rm --privileged aptman/qus -- -r
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64 - 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 - echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
script: script:
- | - |
@ -45,6 +45,7 @@ docker-build-dev:
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")" --build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}" --build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}" --build-arg "CRAFTY_VER=${VERSION}"
--provenance false
--tag "$CI_REGISTRY_IMAGE${tag}" --tag "$CI_REGISTRY_IMAGE${tag}"
--tag "arcadiatechnology/crafty-4${tag}" --tag "arcadiatechnology/crafty-4${tag}"
--platform linux/arm64/v8,linux/amd64 --platform linux/arm64/v8,linux/amd64
@ -84,7 +85,7 @@ docker-build-prod:
docker version docker version
- docker run --rm --privileged aptman/qus -- -r - docker run --rm --privileged aptman/qus -- -r
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64 - 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 - echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
script: script:
- | - |
@ -100,6 +101,7 @@ docker-build-prod:
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")" --build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}" --build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}" --build-arg "CRAFTY_VER=${VERSION}"
--provenance false
--tag "$CI_REGISTRY_IMAGE:$VERSION" --tag "$CI_REGISTRY_IMAGE:$VERSION"
--tag "$CI_REGISTRY_IMAGE:latest" --tag "$CI_REGISTRY_IMAGE:latest"
--tag "arcadiatechnology/crafty-4:$VERSION" --tag "arcadiatechnology/crafty-4:$VERSION"

View File

@ -1,5 +1,5 @@
# Changelog # Changelog
## --- [4.0.23] - 2023/TBD ## --- [4.1.1] - 2023/TBD
### New features ### New features
TBD TBD
### Bug fixes ### Bug fixes
@ -10,6 +10,26 @@ TBD
TBD TBD
<br><br> <br><br>
## --- [4.1.0] - 2023/05/15
### New features
- Mobile PWA App (beta) | Ability to add a Crafty icon to your mobile's home screen ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/576))
- [New Crafty Documentation release](https://docs.craftycontrol.com)
### 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))
- MKDocs Release | Replace wiki names with docs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/583))
### Bug fixes
- 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))
- Fix Cargo errors on Ubuntu 23.04 installs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/579))
- Fix project root error on first start ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/580))
### Tweaks
- Check for python version so we don't just fail out on unsupported python versions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/577))
- Show warning for serverjars API connection issues ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/581))
- Retain pathing in execution command on backup restore ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/578))
<br><br>
## --- [4.0.22] - 2023/04/08 ## --- [4.0.22] - 2023/04/08
### Bug fixes ### 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)) - 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))

View File

@ -67,7 +67,7 @@ LABEL \
org.opencontainers.image.title="Crafty Controller" \ org.opencontainers.image.title="Crafty Controller" \
org.opencontainers.image.description="A Game Server Control Panel / Launcher" \ org.opencontainers.image.description="A Game Server Control Panel / Launcher" \
org.opencontainers.image.url="https://craftycontrol.com/" \ org.opencontainers.image.url="https://craftycontrol.com/" \
org.opencontainers.image.documentation="https://wiki.craftycontrol.com/" \ org.opencontainers.image.documentation="https://docs.craftycontrol.com" \
org.opencontainers.image.source="https://gitlab.com/crafty-controller/crafty-4" \ org.opencontainers.image.source="https://gitlab.com/crafty-controller/crafty-4" \
org.opencontainers.image.vendor="Arcadia Technology, LLC." \ org.opencontainers.image.vendor="Arcadia Technology, LLC." \
org.opencontainers.image.licenses="GPL-3.0" org.opencontainers.image.licenses="GPL-3.0"

View File

@ -1,5 +1,5 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com) [![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
# Crafty Controller 4.0.23 # Crafty Controller 4.1.1
> Python based Control Panel for your Minecraft Server > Python based Control Panel for your Minecraft Server
## What is Crafty Controller? ## What is Crafty Controller?
@ -9,7 +9,7 @@ a web interface for the server administrators to interact with their servers. Cr
is compatible with Docker, Linux, Windows 7, Windows 8 and Windows 10. is compatible with Docker, Linux, Windows 7, Windows 8 and Windows 10.
## Documentation ## Documentation
Documentation available on [wiki.craftycontrol.com](https://craftycontrol.com) Documentation available on [Crafty Docs](https://docs.craftycontrol.com)
## Meta ## Meta
Project Homepage - https://craftycontrol.com Project Homepage - https://craftycontrol.com

View File

@ -253,6 +253,7 @@ class ServersController(metaclass=Singleton):
@staticmethod @staticmethod
def get_authorized_servers(user_id): def get_authorized_servers(user_id):
server_ids = []
server_data: t.List[t.Dict[str, t.Any]] = [] server_data: t.List[t.Dict[str, t.Any]] = []
user_roles = HelperUsers.user_role_query(user_id) user_roles = HelperUsers.user_role_query(user_id)
for user in user_roles: for user in user_roles:
@ -260,11 +261,13 @@ class ServersController(metaclass=Singleton):
user.role_id user.role_id
) )
for role in role_servers: for role in role_servers:
server_data.append( if role.server_id.server_id not in server_ids:
ServersController().get_server_instance_by_id( server_ids.append(role.server_id.server_id)
role.server_id.server_id server_data.append(
ServersController().get_server_instance_by_id(
role.server_id.server_id
)
) )
)
return server_data return server_data
@ -275,11 +278,10 @@ class ServersController(metaclass=Singleton):
for role in roles_list: for role in roles_list:
role_users = HelperUsers.get_users_from_role(role.role_id) role_users = HelperUsers.get_users_from_role(role.role_id)
for user_role in role_users: 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(): for user_id in HelperUsers.get_super_user_list():
user_ids.add(user_id) user_ids.add(user_id)
return user_ids return user_ids
def get_all_servers_stats(self): def get_all_servers_stats(self):

View File

@ -89,6 +89,7 @@ class UsersController:
}, },
}, },
"hints": {"type": "boolean"}, "hints": {"type": "boolean"},
"server_order": {"type": "string"},
} }
# ********************************************************************************** # **********************************************************************************

View File

@ -386,7 +386,7 @@ class HelperUsers:
@staticmethod @staticmethod
def get_users_from_role(role_id): 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 # ApiKeys Methods

View File

@ -16,6 +16,7 @@ import zipfile
import pathlib import pathlib
import ctypes import ctypes
import shutil import shutil
import shlex
import subprocess import subprocess
import itertools import itertools
from datetime import datetime from datetime import datetime
@ -147,6 +148,29 @@ class Helpers:
logger.error(f"Unable to resolve remote bedrock download url! \n{e}") logger.error(f"Unable to resolve remote bedrock download url! \n{e}")
return False 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): def detect_java(self):
if len(self.find_java_installs()) > 0: if len(self.find_java_installs()) > 0:
return True return True
@ -301,6 +325,16 @@ class Helpers:
except Exception: except Exception:
return False return False
@staticmethod
def check_address_status(address):
try:
response = requests.get(address, timeout=2)
return (
response.status_code // 100 == 2
) # Check if the status code starts with 2
except requests.RequestException:
return False
@staticmethod @staticmethod
def check_port(server_port): def check_port(server_port):
try: try:

View File

@ -421,6 +421,7 @@ class TasksManager:
) )
for item in jobs: for item in jobs:
logger.info(f"JOB: {item}") logger.info(f"JOB: {item}")
return task.schedule_id
def remove_all_server_tasks(self, server_id): def remove_all_server_tasks(self, server_id):
schedules = HelpersManagement.get_schedules_by_server(server_id) schedules = HelpersManagement.get_schedules_by_server(server_id)
@ -450,7 +451,6 @@ class TasksManager:
# created task a child of itself. # created task a child of itself.
if str(job_data.get("parent")) == str(sch_id): if str(job_data.get("parent")) == str(sch_id):
job_data["parent"] = None job_data["parent"] = None
HelpersManagement.update_scheduled_task(sch_id, job_data) HelpersManagement.update_scheduled_task(sch_id, job_data)
if not ( if not (
@ -763,31 +763,37 @@ class TasksManager:
def check_for_old_logs(self): def check_for_old_logs(self):
# check for server logs first # check for server logs first
self.controller.servers.check_for_old_logs() self.controller.servers.check_for_old_logs()
# check for crafty logs now try:
logs_path = os.path.join(self.controller.project_root, "logs") # check for crafty logs now
logs_delete_after = int( logs_path = os.path.join(self.controller.project_root, "logs")
self.helper.get_setting("crafty_logs_delete_after_days") logs_delete_after = int(
) self.helper.get_setting("crafty_logs_delete_after_days")
latest_log_files = [ )
"session.log", latest_log_files = [
"schedule.log", "session.log",
"tornado-access.log", "schedule.log",
"session.log", "tornado-access.log",
"commander.log", "session.log",
] "commander.log",
# we won't delete if delete logs after is set to 0 ]
if logs_delete_after != 0: # we won't delete if delete logs after is set to 0
log_files = list( if logs_delete_after != 0:
filter( log_files = list(
lambda val: val not in latest_log_files, filter(
os.listdir(logs_path), lambda val: val not in latest_log_files,
) os.listdir(logs_path),
)
)
for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file)
if Helpers.check_file_exists(
log_file_path
) and Helpers.is_file_older_than_x_days(
log_file_path, logs_delete_after
):
os.remove(log_file_path)
except:
logger.debug(
"Unable to find project root."
" If this issue persists please contact support."
) )
for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file)
if Helpers.check_file_exists(
log_file_path
) and Helpers.is_file_older_than_x_days(
log_file_path, logs_delete_after
):
os.remove(log_file_path)

View File

@ -289,9 +289,9 @@ class AjaxHandler(BaseHandler):
logger.warning("Server ID not found in send_command ajax call") logger.warning("Server ID not found in send_command ajax call")
Console.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) svr_obj = self.controller.servers.get_server_instance_by_id(server_id)
if command == srv_obj.settings["stop_command"]: if command == svr_obj.settings["stop_command"]:
logger.info( logger.info(
"Stop command detected as terminal input - intercepting." "Stop command detected as terminal input - intercepting."
+ f"Starting Crafty's stop process for server with id: {server_id}" + f"Starting Crafty's stop process for server with id: {server_id}"
@ -313,8 +313,8 @@ class AjaxHandler(BaseHandler):
) )
command = None command = None
if command: if command:
if srv_obj.check_running(): if svr_obj.check_running():
srv_obj.send_command(command) svr_obj.send_command(command)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -382,23 +382,6 @@ class AjaxHandler(BaseHandler):
self.controller.cached_login = "login_1.jpg" self.controller.cached_login = "login_1.jpg"
return 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": elif page == "eula":
server_id = self.get_argument("id", None) server_id = self.get_argument("id", None)
svr = self.controller.servers.get_server_instance_by_id(server_id) svr = self.controller.servers.get_server_instance_by_id(server_id)
@ -445,6 +428,21 @@ class AjaxHandler(BaseHandler):
new_server_id new_server_id
) )
new_server_obj.execution_command = server_data["execution_command"] new_server_obj.execution_command = server_data["execution_command"]
# reset executable path
if svr_obj.path in svr_obj.executable:
new_server_obj.executable = str(svr_obj.executable).replace(
svr_obj.path, new_server_obj.path
)
# reset run command path
if svr_obj.path in svr_obj.execution_command:
new_server_obj.execution_command = str(
svr_obj.execution_command
).replace(svr_obj.path, new_server_obj.path)
# reset log path
if svr_obj.path in svr_obj.log_path:
new_server_obj.log_path = str(svr_obj.log_path).replace(
svr_obj.path, new_server_obj.path
)
self.controller.servers.update_server(new_server_obj) self.controller.servers.update_server(new_server_obj)
# preserve backup config # preserve backup config
@ -505,6 +503,21 @@ class AjaxHandler(BaseHandler):
new_server_id new_server_id
) )
new_server_obj.execution_command = server_data["execution_command"] new_server_obj.execution_command = server_data["execution_command"]
# reset executable path
if server_obj.path in server_obj.executable:
new_server_obj.executable = str(server_obj.executable).replace(
server_obj.path, new_server_obj.path
)
# reset run command path
if server_obj.path in server_obj.execution_command:
new_server_obj.execution_command = str(
server_obj.execution_command
).replace(server_obj.path, new_server_obj.path)
# reset log path
if server_obj.path in server_obj.log_path:
new_server_obj.log_path = str(server_obj.log_path).replace(
server_obj.path, new_server_obj.path
)
self.controller.servers.update_server(new_server_obj) self.controller.servers.update_server(new_server_obj)
# preserve backup config # preserve backup config
@ -624,12 +637,6 @@ class AjaxHandler(BaseHandler):
user_perms = self.controller.server_perms.get_user_id_permissions_list( user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id 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 page == "del_backup":
if not permissions["Backup"] in user_perms: if not permissions["Backup"] in user_perms:
@ -668,84 +675,6 @@ class AjaxHandler(BaseHandler):
): ):
os.remove(file_path) 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): def check_server_id(self, server_id, page_name):
if server_id is None: if server_id is None:
logger.warning( logger.warning(

View File

@ -6,7 +6,6 @@ import typing as t
import json import json
import logging import logging
import threading import threading
import shlex
import urllib.parse import urllib.parse
import bleach import bleach
import requests import requests
@ -17,7 +16,6 @@ from tornado import iostream
# TZLocal is set as a hidden import on win pipeline # TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone from tzlocal import get_localzone
from tzlocal.utils import ZoneInfoNotFoundError from tzlocal.utils import ZoneInfoNotFoundError
from croniter import croniter
from app.classes.models.servers import Servers from app.classes.models.servers import Servers
from app.classes.models.server_permissions import EnumPermissionsServer from app.classes.models.server_permissions import EnumPermissionsServer
@ -256,7 +254,12 @@ class PanelHandler(BaseHandler):
user_order = user_order["server_order"].split(",") user_order = user_order["server_order"].split(",")
page_servers = [] page_servers = []
server_ids = [] 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_id in user_order[:]:
for server in defined_servers[:]: for server in defined_servers[:]:
if str(server.server_id) == str(server_id): if str(server.server_id) == str(server_id):
@ -265,14 +268,7 @@ class PanelHandler(BaseHandler):
) )
user_order.remove(server_id) user_order.remove(server_id)
defined_servers.remove(server) defined_servers.remove(server)
break
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)
)
for server_id in user_order[:]: for server_id in user_order[:]:
# remove IDs in list that user no longer has access to # remove IDs in list that user no longer has access to
if str(server_id) not in server_ids: if str(server_id) not in server_ids:
@ -452,6 +448,7 @@ class PanelHandler(BaseHandler):
page_servers.append(server) page_servers.append(server)
un_used_servers.remove(server) un_used_servers.remove(server)
user_order.remove(server_id) user_order.remove(server_id)
break
# we only want to set these server stats values once. # we only want to set these server stats values once.
# We need to update the flag so it only hits that if once. # We need to update the flag so it only hits that if once.
flag += 1 flag += 1
@ -1080,7 +1077,7 @@ class PanelHandler(BaseHandler):
page_data["schedule"]["cron_string"] = "" page_data["schedule"]["cron_string"] = ""
page_data["schedule"]["delay"] = 0 page_data["schedule"]["delay"] = 0
page_data["schedule"]["time"] = "" page_data["schedule"]["time"] = ""
page_data["schedule"]["interval"] = "" page_data["schedule"]["interval"] = 1
# we don't need to check difficulty here. # we don't need to check difficulty here.
# We'll just default to basic for new schedules # We'll just default to basic for new schedules
page_data["schedule"]["difficulty"] = "basic" page_data["schedule"]["difficulty"] = "basic"
@ -1583,156 +1580,6 @@ class PanelHandler(BaseHandler):
role = self.controller.roles.get_role(r) role = self.controller.roles.get_role(r)
exec_user_role.add(role["role_name"]) 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": if page == "server_backup":
logger.debug(self.request.arguments) logger.debug(self.request.arguments)
@ -1829,336 +1676,6 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/config_json") 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": elif page == "edit_user":
if bleach.clean(self.get_argument("username", None)).lower() == "system": if bleach.clean(self.get_argument("username", None)).lower() == "system":
self.redirect( self.redirect(

View File

@ -50,12 +50,15 @@ class PublicHandler(BaseHandler):
if page == "login": if page == "login":
template = "public/login.html" template = "public/login.html"
elif page == 404: elif page == "404":
template = "public/404.html" template = "public/404.html"
elif page == "error": elif page == "error":
template = "public/error.html" template = "public/error.html"
elif page == "offline":
template = "public/offline.html"
elif page == "logout": elif page == "logout":
self.clear_cookie("token") self.clear_cookie("token")
# self.clear_cookie("user") # self.clear_cookie("user")

View File

@ -4,6 +4,36 @@ from peewee import DoesNotExist
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
modify_role_schema = { 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", "type": "object",
"properties": { "properties": {
"name": { "name": {
@ -109,7 +139,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
) )
try: 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: except ValidationError as e:
return self.finish_json( return self.finish_json(
400, 400,

View File

@ -13,20 +13,39 @@ server_patch_schema = {
"type": "object", "type": "object",
"properties": { "properties": {
"server_name": {"type": "string", "minLength": 1}, "server_name": {"type": "string", "minLength": 1},
"path": {"type": "string", "minLength": 1},
"backup_path": {"type": "string"}, "backup_path": {"type": "string"},
"executable": {"type": "string"}, "executable": {"type": "string"},
"log_path": {"type": "string", "minLength": 1}, "log_path": {"type": "string", "minLength": 1},
"execution_command": {"type": "string", "minLength": 1}, "execution_command": {"type": "string", "minLength": 1},
"java_selection": {"type": "string"},
"auto_start": {"type": "boolean"}, "auto_start": {"type": "boolean"},
"auto_start_delay": {"type": "integer"}, "auto_start_delay": {"type": "integer", "minimum": 0},
"crash_detection": {"type": "boolean"}, "crash_detection": {"type": "boolean"},
"stop_command": {"type": "string"}, "stop_command": {"type": "string"},
"executable_update_url": {"type": "string", "minLength": 1}, "executable_update_url": {"type": "string"},
"server_ip": {"type": "string", "minLength": 1}, "server_ip": {"type": "string", "minLength": 1},
"server_port": {"type": "integer"}, "server_port": {"type": "integer"},
"logs_delete_after": {"type": "integer"}, "shutdown_timeout": {"type": "integer", "minimum": 0},
"type": {"type": "string", "minLength": 1}, "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, "additionalProperties": False,
"minProperties": 1, "minProperties": 1,
@ -63,7 +82,11 @@ class ApiServersServerIndexHandler(BaseApiHandler):
) )
try: 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: except ValidationError as e:
return self.finish_json( return self.finish_json(
400, 400,
@ -88,9 +111,24 @@ class ApiServersServerIndexHandler(BaseApiHandler):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
server_obj = self.controller.servers.get_server_obj(server_id) server_obj = self.controller.servers.get_server_obj(server_id)
java_flag = False
for key in data: for key in data:
# If we don't validate the input there could be security issues # 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 != "path":
if key == "execution_command" and java_flag:
continue
setattr(server_obj, key, data[key]) setattr(server_obj, key, data[key])
self.controller.servers.update_server(server_obj) self.controller.servers.update_server(server_obj)
@ -134,7 +172,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
) )
self.tasks_manager.remove_all_server_tasks(server_id) 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( self.controller.management.add_to_audit_log(
auth_data[4]["user_id"], auth_data[4]["user_id"],

View File

@ -35,7 +35,13 @@ class ApiServersServerStdinHandler(BaseApiHandler):
"Please report this to the devs" "Please report this to the devs"
) )
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) 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")): if svr.send_command(self.request.body.decode("utf-8")):
return self.finish_json( return self.finish_json(
200, 200,

View File

@ -1,16 +1,121 @@
# TODO: create and read # TODO: create and read
import json
import logging 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 from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__) 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): class ApiServersServerTasksIndexHandler(BaseApiHandler):
def get(self, server_id: str, task_id: str): def get(self, server_id: str, task_id: str):
pass pass
def post(self, server_id: str, task_id: str): def post(self, server_id: str):
pass 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}})

View File

@ -3,6 +3,7 @@
import json import json
import logging import logging
from croniter import croniter
from jsonschema import ValidationError, validate from jsonschema import ValidationError, validate
from app.classes.models.server_permissions import EnumPermissionsServer 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}"}, "start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
"command": {"type": ["string", "null"]}, "command": {"type": ["string", "null"]},
"one_time": {"type": "boolean", "default": False}, "one_time": {"type": "boolean", "default": False},
@ -49,10 +51,47 @@ task_patch_schema = {
class ApiServersServerTasksTaskIndexHandler(BaseApiHandler): class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
def get(self, server_id: str, task_id: str): 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): 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): def patch(self, server_id: str, task_id: str):
auth_data = self.authenticate_user() 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: if str(data.get("parent")) == str(task_id) and data.get("parent") is not None:
data["parent"] = 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.tasks_manager.update_job(task_id, data)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(

View File

@ -143,7 +143,11 @@ class ServerHandler(BaseHandler):
"not a server creator or server limit reached" "not a server creator or server limit reached"
) )
return return
page_data["server_api"] = False
if page_data["online"]:
page_data["server_api"] = self.helper.check_address_status(
"https://serverjars.com/api/fetchTypes"
)
page_data["server_types"] = self.controller.server_jars.get_serverjar_data() page_data["server_types"] = self.controller.server_jars.get_serverjar_data()
page_data["js_server_types"] = json.dumps( page_data["js_server_types"] = json.dumps(
self.controller.server_jars.get_serverjar_data() self.controller.server_jars.get_serverjar_data()
@ -333,7 +337,7 @@ class ServerHandler(BaseHandler):
if import_type == "import_jar": if import_type == "import_jar":
if self.helper.is_subdir( if self.helper.is_subdir(
import_server_path, self.controller.project_root self.controller.project_root, import_server_path
): ):
self.redirect( self.redirect(
"/panel/error?error=Loop Error: The selected path will cause" "/panel/error?error=Loop Error: The selected path will cause"
@ -499,7 +503,7 @@ class ServerHandler(BaseHandler):
if import_type == "import_jar": if import_type == "import_jar":
if self.helper.is_subdir( if self.helper.is_subdir(
import_server_path, self.controller.project_root self.controller.project_root, import_server_path
): ):
self.redirect( self.redirect(
"/panel/error?error=Loop Error: The selected path will cause" "/panel/error?error=Loop Error: The selected path will cause"

View File

@ -11,6 +11,9 @@ except ModuleNotFoundError as e:
class CustomStaticHandler(tornado.web.StaticFileHandler): class CustomStaticHandler(tornado.web.StaticFileHandler):
def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
# This is for the mobile app service worker
if self.request.path.find("service-worker.js") != -1:
self.set_header("Service-Worker-Allowed", "/")
try: try:
return super().validate_absolute_path(root, absolute_path) return super().validate_absolute_path(root, absolute_path)
except tornado.web.HTTPError as error: except tornado.web.HTTPError as error:

View File

@ -1,5 +1,5 @@
{ {
"major": 4, "major": 4,
"minor": 0, "minor": 1,
"sub": 23 "sub": 1
} }

View File

@ -0,0 +1,40 @@
{
"background_color": "#222436",
"description": "Crafty Controller is a free and open-source Minecraft launcher and manager that allows users to start and administer Minecraft servers from a user-friendly interface.",
"dir": "ltr",
"display": "standalone",
"name": "Crafty Controller",
"orientation": "any",
"scope": "/",
"short_name": "Crafty",
"start_url": "/",
"theme_color": "#222436",
"categories": ["utilities"],
"icons": [
{
"src": "/static/assets/images/Crafty_4-0_Logo_square.ico",
"type": "image/x-icon",
"sizes":"128x128"
},
{
"src": "/static/assets/images/Crafty_4-0.png",
"type": "image/png",
"sizes": "144x144",
"purpose": "any"
},
{
"src": "/static/assets/images/crafty-logo-square-1024.png",
"type": "image/png",
"sizes": "1024x1024",
"purpose": "any"
},
{
"src": "/static/assets/images/crafty-logo-square-96.png",
"type": "image/png",
"sizes": "96x96",
"purpose": "any"
}
],
"lang": "en",
"prefer_related_applications": false
}

View File

@ -149,10 +149,6 @@ div>.input-group>.custom-file-input {
border: 1px solid var(--outline); border: 1px solid var(--outline);
} }
.input-group>.input-group-append>button {
height: calc(1.5em + 0.75rem + 2px);
}
div>.input-group>.form-control-file { div>.input-group>.form-control-file {
position: relative !important; position: relative !important;
-webkit-box-flex: 1 !important; -webkit-box-flex: 1 !important;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,46 @@
// This is the "Offline page" service worker
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
const CACHE = "crafty-controller";
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = "/offline";
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
self.addEventListener('install', async (event) => {
event.waitUntil(
caches.open(CACHE)
.then((cache) => cache.add(offlineFallbackPage))
);
});
if (workbox.navigationPreload.isSupported()) {
workbox.navigationPreload.enable();
}
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResp = await event.preloadResponse;
if (preloadResp) {
return preloadResp;
}
const networkResp = await fetch(event.request);
return networkResp;
} catch (error) {
const cache = await caches.open(CACHE);
const cachedResp = await cache.match(offlineFallbackPage);
return cachedResp;
}
})());
}
});

View File

@ -18,6 +18,16 @@
href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" /> href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" />
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css"> <link rel="stylesheet" href="/static/assets/css/crafty.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<link rel="shortcut icon" sizes="192x192" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
@ -247,7 +257,7 @@
const sendWssError = () => wsOpen || warn( const sendWssError = () => wsOpen || warn(
'WebSockets are required for Crafty to work. This websocket connection has been closed. Are you using a reverse proxy?', 'WebSockets are required for Crafty to work. This websocket connection has been closed. Are you using a reverse proxy?',
'https://wiki.craftycontrol.com/en/4/docs/Reverse%20Proxy%20Examples', 'https://docs.craftycontrol.com/pages/getting-started/proxies/',
'wssError' 'wssError'
) )
@ -526,6 +536,14 @@
}); });
}); });
$(document).ready(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.log('Service Worker Registered');
});
}
});
</script> </script>
{% block js %} {% block js %}

View File

@ -14,6 +14,13 @@
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css"> <link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css">
<link rel="stylesheet" href="/static/assest/css/crafty.css"> <link rel="stylesheet" href="/static/assest/css/crafty.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->

View File

@ -100,7 +100,7 @@
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="https://wiki.craftycontrol.com" target="_blank"> <a class="nav-link" href="https://docs.craftycontrol.com" target="_blank">
<i class="fas fa-book"></i> &nbsp; <i class="fas fa-book"></i> &nbsp;
<span class="menu-title">{{ translate('sidebar', 'documentation', data['lang']) }}</span> <span class="menu-title">{{ translate('sidebar', 'documentation', data['lang']) }}</span>
</a> </a>
@ -109,7 +109,7 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/panel/wiki"> <a class="nav-link" href="/panel/wiki">
<i class="fa fa-info-circle"></i> &nbsp; <i class="fa fa-info-circle"></i> &nbsp;
<span class="menu-title">Wiki</span> <span class="menu-title">{{ translate('sidebar', 'inApp', data['lang']) }}</span>
</a> </a>
</li> </li>

View File

@ -66,7 +66,7 @@
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('customLogin', 'labelLoginImage', data['lang']) }}</label> <label id="fileLabel" class="custom-file-label" for="file">{{ translate('customLogin', 'labelLoginImage', data['lang']) }}</label>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-info" id="upload-button" onclick="sendFile()" disabled>UPLOAD</button> <button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()" disabled>UPLOAD</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -647,10 +647,13 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + server_id, url: `/api/v2/servers/${server_id}/action/${command}`,
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);
if (command === "clone_server" && data.status === "ok") {
window.location.reload();
}
/*setTimeout(function () { /*setTimeout(function () {
if (command != 'start_server') { if (command != 'start_server') {
location.reload(); location.reload();
@ -705,24 +708,6 @@
document.querySelector('.dynamicMsg').appendChild(parentEl); 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) { function update_one_server_status(server) {
/* Mobile view update */ /* Mobile view update */
server_cpu = document.getElementById('server_cpu_' + server.id); server_cpu = document.getElementById('server_cpu_' + server.id);
@ -901,17 +886,11 @@
}, },
callback: function (result) { callback: function (result) {
if (result) { if (result) {
send_kill(server_id); send_command(server_id, "kill_server");
let dialog = bootbox.dialog({ let dialog = bootbox.dialog({
title: '{% raw translate("dashboard", "killing", data["lang"]) %}', title: '{% raw translate("dashboard", "killing", data["lang"]) %}',
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>' message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
}); });
dialog.init(function () {
setTimeout(function () {
location.reload();
}, 15000);
});
} }
} }
}); });
@ -1000,7 +979,13 @@
}, },
callback: function (result) { callback: function (result) {
if (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> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
closeButton: false,
});
} }
} }
@ -1008,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> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
closeButton: false,
});
}
</script> </script>
<script src="/static/assets/vendors/js/jquery-ui.js"></script> <script src="/static/assets/vendors/js/jquery-ui.js"></script>
<link rel="stylesheet" href="/static/assets/vendors/css/jquery-ui.css"> <link rel="stylesheet" href="/static/assets/vendors/css/jquery-ui.css">
@ -1069,12 +1044,12 @@
const token = getCookie("_xsrf") const token = getCookie("_xsrf")
$.ajax({ $.ajax({
type: "POST", type: "PATCH",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/send_order?order=' + id_string, url: `/api/v2/users/@me`,
data: { data: JSON.stringify({
order: id_string, server_order: id_string,
}, }),
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);

View File

@ -12,6 +12,15 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +33,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>
@ -77,6 +86,21 @@
<script src="/static/assets/js/shared/settings.js"></script> <script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script> <script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject --> <!-- endinject -->
<script>
$(document).ready(function () {
let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.error('Service Worker Registered');
});
}
});
</script>
</body> </body>
</html> </html>

View File

@ -321,9 +321,60 @@
return r ? r[1] : undefined; 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() { $( document ).ready(function() {
console.log( "ready!" ); 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> </script>

View File

@ -14,7 +14,8 @@
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title"> <h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }} {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4> </h4>
@ -83,10 +84,14 @@
<li class="playerItem"> <li class="playerItem">
<h3>{{ player }}</h3> <h3>{{ player }}</h3>
<div class="buttons"> <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('ban {{ player }}')" type="button"
<button onclick="send_command_to_server('kick {{ player }}')" type="button" class="btn btn-outline-danger">Kick</button> class="btn btn-danger">Ban</button>
<button onclick="send_command_to_server('op {{ player }}')" type="button" class="btn btn-warning">OP</button> <button onclick="send_command_to_server('kick {{ player }}')" type="button"
<button onclick="send_command_to_server('deop {{ player }}')" type="button" class="btn btn-outline-warning">De-OP</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> </div>
</li> </li>
{% end %} {% end %}
@ -143,21 +148,22 @@
}); });
function send_command_to_server(command) { async function send_command_to_server(command) {
console.log(command) console.log(command)
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
console.log('sending command: ' + command) console.log('sending command: ' + command)
$.ajax({ let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
type: "POST", method: 'POST',
headers: { 'X-XSRFToken': token }, headers: {
url: '/ajax/send_command?id=' + serverId, 'X-XSRFToken': token
data: { command },
success: function (data) {
console.log("got response:");
console.log(data);
}, },
body: command,
}); });
let responseData = await res.text();
console.log("got response:");
console.log(responseData);
} }

View File

@ -326,7 +326,7 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/backup_now?id=' + server_id, url: `/api/v2/servers/${server_id}/action/backup_server`,
success: function (data) { success: function (data) {
return; return;
}, },

View File

@ -43,24 +43,17 @@
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<form class="forms-sample" method="post" id="config_form" action="/panel/server_detail"> <form class="forms-sample" method="post" id="config_form">
{% 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"> <div class="form-group">
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small <label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small>
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small>
</label> </label>
<input type="text" class="form-control" name="server_name" id="server_name" <input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['server_name'] }}"
placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
</div> </div>
{% if data['super_user'] %} {% if data['super_user'] %}
<div class="form-group"> <div class="form-group">
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small <label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
</label> </label>
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<span style="color: gray; font-size: 12px;">{{ data['server_stats']['server_id']['path'] }}</span> <span style="color: gray; font-size: 12px;">{{ data['server_stats']['server_id']['path'] }}</span>
@ -70,22 +63,16 @@
</div> </div>
{% if data['server_stats']['server_type'] != "minecraft-bedrock" %} {% if data['server_stats']['server_type'] != "minecraft-bedrock" %}
<div class="form-group"> <div class="form-group">
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small <label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang'])
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="log_path" id="log_path" <input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['log_path'] }}"
placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
</div> </div>
{% end %} {% end %}
<div class="form-group"> <div class="form-group">
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small <label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang'])
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="executable" id="executable" <input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['executable'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
</div> </div>
{% end %} {% end %}
{% if data['server_stats']['server_type'] == "minecraft-java" %} {% if data['server_stats']['server_type'] == "minecraft-java" %}
@ -94,10 +81,8 @@
<small class="text-muted ml-1">{{ translate('serverConfig', 'javaVersionDesc', data['lang']) <small class="text-muted ml-1">{{ translate('serverConfig', 'javaVersionDesc', data['lang'])
}}</small> }}</small>
</label> </label>
<select class="form-select form-control form-control-lg select-css" id="java_selection" <select class="form-select form-control form-control-lg select-css" id="java_selection" name="java_selection" form="config_form">
name="java_selection" form="config_form"> <option value="none">{{ translate('serverConfig', 'javaNoChange', data['lang'])}}</option>
<option value="">{{ translate('serverConfig',
'javaNoChange', data['lang'])}}</option>
{% for path in data['java_versions'] %} {% for path in data['java_versions'] %}
<option value="{{path}}">{{path}}</option> <option value="{{path}}">{{path}}</option>
{% end %} {% end %}
@ -110,9 +95,7 @@
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc', <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc',
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="text" class="form-control" name="execution_command" id="execution_command" <input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats']['server_id']['execution_command'] }}" placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['execution_command'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
</div> </div>
{% else %} {% else %}
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
@ -122,21 +105,16 @@
<br> <br>
{% end %} {% end %}
<div class="form-group"> <div class="form-group">
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small <label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang'])
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="stop_command" id="stop_command" <input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['stop_command'] }}"
placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }} <label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc', <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc',
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" <input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" required>
value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10"
required>
</div> </div>
{% if data['super_user'] %} {% if data['super_user'] %}
@ -145,31 +123,21 @@
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }} <label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang']) <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" <input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
value="{{ data['server_stats']['server_id']['executable_update_url'] }}"
placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
</div> </div>
{% end %} {% end %}
<div class="form-group"> <div class="form-group">
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small <label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small>
class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small>
</label> </label>
<input type="text" class="form-control" name="server_ip" id="server_ip" <input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small <label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}
</small> </label> </small> </label>
<input type="number" class="form-control" name="server_port" id="server_port" <input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" required>
value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" <span data-html="true" class="port-hint text-center" title="<i class='fal fa-exclamation-triangle'></i> " , data-content="{{
required> translate('serverConfig', 'statsHint1' , data['lang'])}} <br> <br> <strong>{{ translate('serverConfig', 'statsHint2', data['lang'])}}</strong>" , data-placement="right"></span>
<span data-html="true" class="port-hint text-center"
title="<i class='fal fa-exclamation-triangle'></i> " ,
data-content="{{
translate('serverConfig', 'statsHint1' , data['lang'])}} <br> <br> <strong>{{ translate('serverConfig', 'statsHint2', data['lang'])}}</strong>" ,
data-placement="right"></span>
</div> </div>
{% end %} {% end %}
@ -180,9 +148,7 @@
{{ data['server_stats']['server_id']['stop_command'] }}&nbsp;{{ translate('serverConfig', {{ data['server_stats']['server_id']['stop_command'] }}&nbsp;{{ translate('serverConfig',
'timeoutExplain2', data['lang']) }} 'timeoutExplain2', data['lang']) }}
</small> </label> </small> </label>
<input type="number" class="form-control" name="shutdown_timeout" id="shutdown_timeout" <input type="number" class="form-control" name="shutdown_timeout" id="shutdown_timeout" value="{{ data['server_stats']['server_id']['shutdown_timeout'] }}" step="2" max="300" min="60" required>
value="{{ data['server_stats']['server_id']['shutdown_timeout'] }}" step="2" max="300" min="60"
required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="ignored_exits">{{ translate('serverConfig', 'ignoredExits', data['lang']) }} <label for="ignored_exits">{{ translate('serverConfig', 'ignoredExits', data['lang']) }}
@ -190,17 +156,14 @@
data['lang']) data['lang'])
}} }}
</small> </label> </small> </label>
<input type="text" class="form-control" name="ignored_exits" id="ignored_exits" <input type="text" class="form-control" name="ignored_exits" id="ignored_exits" value="{{ data['server_stats']['server_id']['ignored_exits'] }}">
value="{{ data['server_stats']['server_id']['ignored_exits'] }}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc', <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc',
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" <input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" required>
value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0"
required>
</div> </div>
<!-- <!--
@ -303,14 +266,10 @@
<div class="text-center"> <div class="text-center">
{% if data['server_stats']['running'] %} {% if data['server_stats']['running'] %}
{% if data['server_stats']['updating'] %} {% if data['server_stats']['updating'] %}
<i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% else %} {% else %}
<i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% end %} {% end %}
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang']) <a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang'])
@ -319,14 +278,10 @@
{% else %} {% else %}
{% if not data['failed'] %} {% if not data['failed'] %}
{% if data['server_stats']['updating'] %} {% if data['server_stats']['updating'] %}
<i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% else %} {% else %}
<i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% end %} {% end %}
{% end %} {% end %}
@ -395,7 +350,7 @@
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/delete_server?id=' + serverId, url: `/api/v2/servers/${serverId}`,
data: { data: {
}, },
success: function (data) { success: function (data) {
@ -409,7 +364,7 @@
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/delete_server_files?id=' + serverId, url: `/api/v2/servers/${serverId}?files=true`,
data: { data: {
}, },
success: function (data) { success: function (data) {
@ -429,7 +384,7 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + serverId, url: `/api/v2/servers/${serverId}/action/${command}`,
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);
@ -558,7 +513,7 @@
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/delete_unloaded_server?id=' + serverId, url: `/api/v2/servers/${serverId}`,
data: { data: {
}, },
success: function (data) { success: function (data) {
@ -586,11 +541,92 @@
$('.port-hint').popover("hide"); $('.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 () { $(document).ready(function () {
let token = getCookie("_xsrf")
webSocket.on('remove_spinner', function () { webSocket.on('remove_spinner', function () {
document.getElementById("update-spinner").style.visibility = "hidden"; 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> </script>

View File

@ -37,15 +37,12 @@
<div class="row"> <div class="row">
<div class="col-md-8 col-sm-8"> <div class="col-md-8 col-sm-8">
{% if data['new_schedule'] == True %} {% 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'] }}"> action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
{% else %} {% 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'] }}"> action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
{% end %} {% 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"> <div class="form-group">
<label for="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label> <label for="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label>
@ -89,7 +86,7 @@
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' , class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' ,
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="number" class="form-control" name="interval" id="interval" <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>
<br> <br>
<select id="interval_type" onchange="ifDays(this);" name="interval_type" <select id="interval_type" onchange="ifDays(this);" name="interval_type"
@ -108,7 +105,7 @@
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small <label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' , class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' ,
data['lang']) }}</small> </label> 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> value="{{ data['schedule']['time'] }}" placeholder="Time" required>
</div> </div>
</div> </div>
@ -127,7 +124,7 @@
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small <label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang']) class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang'])
}}</small> </label> }}</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="* * * * *"> value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
</div> </div>
</div> </div>
@ -234,8 +231,120 @@
return r ? r[1] : undefined; 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 () { $(document).ready(function () {
console.log("ready!"); 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("parent").required = true;
document.getElementById("interval").required = false; document.getElementById("interval").required = false;
document.getElementById("time").required = false; document.getElementById("time").required = false;
$("#cron").val("");
} }
else { else {
document.getElementById("ifAdvanced").style.display = "none"; document.getElementById("ifAdvanced").style.display = "none";
@ -274,6 +384,7 @@
document.getElementById("parent").required = false; document.getElementById("parent").required = false;
document.getElementById("interval").required = true; document.getElementById("interval").required = true;
document.getElementById("time").required = true; document.getElementById("time").required = true;
$("#cron").val("");
} }
} }
function ifDays() { 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() { function startup() {
try { try {
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true); document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);

View File

@ -86,7 +86,7 @@
<p>{{schedule.command}}</p> <p>{{schedule.command}}</p>
</td> </td>
<td id="{{schedule.interval}}" class="action"> <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>{{ translate('serverSchedules', 'every', data['lang']) }}</p>
<p>{{schedule.interval}} {{schedule.interval_type}}</p> <p>{{schedule.interval}} {{schedule.interval_type}}</p>
{% elif schedule.interval_type == 'reaction' %} {% elif schedule.interval_type == 'reaction' %}
@ -425,21 +425,19 @@
}); });
}); });
function del_task(sch_id, id) { async function del_task(sch_id, id) {
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
$.ajax({ let res = await fetch(`/api/v2/servers/${id}/tasks/${sch_id}`, {
type: "DELETE", method: 'DELETE',
headers: { 'X-XSRFToken': token }, headers: {
url: '/ajax/del_task?server_id=' + id + '&schedule_id=' + sch_id, 'token': token,
data: {
schedule_id: sch_id,
id: id
},
success: function (data) {
location.reload();
}, },
}); });
let responseData = await res;
if (responseData.statusText === "OK") {
window.location.reload();
}
} }
</script> </script>

View File

@ -178,7 +178,7 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + serverId, url: `/api/v2/servers/${serverId}/action/${command}`,
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);
@ -310,12 +310,12 @@
formdata.append('command', serverCommand) formdata.append('command', serverCommand)
console.log('sending 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', method: 'POST',
headers: { headers: {
'X-XSRFToken': token 'X-XSRFToken': token
}, },
body: formdata, body: serverCommand,
}); });
let responseData = await res.text(); let responseData = await res.text();

View File

@ -13,14 +13,14 @@
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title">Wiki</h4> <h4 class="page-title">{{ translate('sidebar', 'documentation', data['lang']) }}</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12 grid-margin"> <div class="col-md-12 grid-margin">
<iframe src="https://wiki.craftycontrol.com" width=100% height=2200px title="crafty's wiki"></iframe> <iframe src="https://docs.craftycontrol.com/" width=100% height=1100px title="crafty's docs"></iframe>
</div> </div>

View File

@ -12,6 +12,14 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +32,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>

View File

@ -12,6 +12,14 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +32,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>

View File

@ -12,6 +12,13 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- <meta name="apple-mobile-web-app-title" content="Crafty Controller 4"> -->
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +31,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
background-position: center; background-position: center;
} }
@ -140,7 +147,15 @@
let login_opacity_div = document.getElementById('login_opacity'); let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value'); let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')'; document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.error('Service Worker Registered');
});
}
}); });
</script> </script>
</body> </body>

View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Crafty Controller</title>
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head>
<style>
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("/static/assets/images/auth/login_1.jpg");
background-size: cover;
}
</style>
<body class="dark-theme">
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
<div class="row w-100">
<div class="col-lg-4 mx-auto">
<div class="auto-form-wrapper">
<div class="text-center">
<img src="/static/assets/images/logo_long.svg"><br /><br />
<div class="col-sm-12 grid-margin stretch-card">
<div class="card card-statistics social-card facebook-card card-colored">
<div class="card-body">
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('offline', 'offline', data['lang']) }}</h4>
<h5 class="headline font-weight-medium"></h5>
<p class="mb-2 comment font-weight-light">
{{ translate('offline', 'pleaseConnect', data['lang']) }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
</div>
<!-- page-body-wrapper ends -->
</div>
<!-- container-scroller -->
<!-- plugins:js -->
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<!-- endinject -->
<!-- inject:js -->
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject -->
</body>
</html>

View File

@ -10,7 +10,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>
@ -89,7 +89,7 @@
</div> </div>
<!-- View for Small screen --> <!-- View for Small screen -->
<div class="row justify-content-center align-items-sm-center"> <div class="row justify-content-center align-items-sm-center">
<div class="content-wrapper login-modal d-sm-none d-block"> <div class="content-wrapper login-modal d-sm-none d-block" style="background-color: var(--dropdown-bg);">
<img src="/static/assets/images/logo_long.png" style='width: 100%;'> <img src="/static/assets/images/logo_long.png" style='width: 100%;'>
<hr /> <hr />
{% if data['running'] != 0 %} {% if data['running'] != 0 %}

View File

@ -14,6 +14,13 @@
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css"> <link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -98,7 +105,7 @@
usingWebSockets = false; usingWebSockets = false;
} }
// {% else %} // {% else %}
let usingWebSockets = false; usingWebSockets = false;
warn('WebSockets are not supported in Crafty if not using the https protocol') warn('WebSockets are not supported in Crafty if not using the https protocol')
var webSocket; var webSocket;
// {% end%} // {% end%}
@ -106,6 +113,21 @@
</script> </script>
{% block js %} {% block js %}
<!-- Custom js for this page --> <!-- Custom js for this page -->
<script>
$(document).ready(function () {
let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.error('Service Worker Registered');
});
}
});
</script>
<!-- End custom js for this page --> <!-- End custom js for this page -->
{% end %} {% end %}

View File

@ -260,7 +260,7 @@
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('serverWizard', 'labelZipFile', data['lang']) }}</label> <label id="fileLabel" class="custom-file-label" for="file">{{ translate('serverWizard', 'labelZipFile', data['lang']) }}</label>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-info" id="upload-button" onclick="sendFile()" disabled>{{ translate('serverWizard', <button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()" disabled>{{ translate('serverWizard',
'uploadButton', data['lang']) }}</button> 'uploadButton', data['lang']) }}</button>
</div> </div>
</div> </div>
@ -372,10 +372,6 @@
border: 1px solid var(--outline); border: 1px solid var(--outline);
} }
.input-group>.input-group-append>button {
height: calc(1.5em + 0.75rem + 2px);
}
.scroll { .scroll {
max-height: 12em; max-height: 12em;
overflow-y: auto; overflow-y: auto;

View File

@ -20,7 +20,7 @@
<!-- Create New Server --> <!-- Create New Server -->
{% if data['online'] %} {% if data['online'] %}
<div class="col-md-6 grid-margin stretch-card"> <div class="col-md-6 grid-margin stretch-card">
<div class="card"> <div class="card" id="creation_wizard">
<div class="card-body"> <div class="card-body">
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4> <h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
@ -28,122 +28,157 @@
<p class="card-description"> <p class="card-description">
<form method="post" class="server-wizard" onSubmit="wait_msg()"> <form method="post" class="server-wizard" onSubmit="wait_msg()">
{% raw xsrf_form_html() %} {% if data["server_api"] %}
<div class="row"> <fieldset>
<div class="col-sm-12"> {% else %}
<div class="form-group"> <fieldset disabled="disabled">
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang']) <style>
}}</label> #creation_wizard {
{% if data['super_user'] %} -webkit-filter: grayscale(1);
<div class="input-group"> }
<select required class="form-control form-control-lg select-css" id="server_jar" name="server_jar" onchange="serverJarChange(this)">
{% else %} .api-alert {
<select required class="form-control form-control-lg select-css" id="server_jar" name="server_jar" onchange="serverJarChange(this)"> position: absolute;
top: -5px;
left: 0;
font-size: 50px !important;
color: #fff;
background: rgb(0, 170, 170);
opacity: .4;
width: 100%;
height: 100%;
z-index: 100;
}
.api-alert p {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
text-align: center;
font-size: 20px;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
</style>
{% end %}
{% raw xsrf_form_html() %}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang'])
}}</label>
<div class="input-group">
<select required class="form-control form-control-lg select-css" id="server_jar" name="server_jar" onchange="serverJarChange(this)">
<option value="None">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
{% for s in data['server_types'] %}
<option value="{{ s }}">{{ s.capitalize() }}</option>
{% end %}
</select>
{% if data['super_user'] %}
<div class="input-group-append">
<button class="btn custom-picker" type="button" onclick="refreshCache()"><i id="refresh-cache" class="refresh-class fas fa-sync"></i></button>
</div>
{% end %} {% end %}
<option value="None">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
{% for s in data['server_types'] %}
<option value="{{ s }}">{{ s.capitalize() }}</option>
{% end %}
</select>
{% if data['super_user'] %}
<div class="input-group-append">
<button class="btn custom-picker" type="button" onclick="refreshCache()"><i id="refresh-cache" class="refresh-class fas fa-sync"></i></button>
</div> </div>
</div>
</div> </div>
{% end %}
</div>
</div>
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="server_type">{{ translate('serverWizard', 'serverSelect', data['lang']) }}</label> <label for="server_type">{{ translate('serverWizard', 'serverSelect', data['lang']) }}</label>
<select required class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)"> <select required class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)">
<option value="">{{ translate('serverWizard', 'selectServer', data['lang']) }}</option> <option value="">{{ translate('serverWizard', 'selectServer', data['lang']) }}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label> <label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label>
<select required class="form-control form-control-lg select-css" id="server" name="server"> <select required class="form-control form-control-lg select-css" id="server" name="server">
<option value="">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option> <option value="">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label> <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required> <input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div> </div>
</div> </div>
</div>
<br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4>
<hr>
<div class="row">
<div class="col-sm-4">
<div class="form-group">
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" min="0.5" required>
</div> </div>
</div> <br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4>
<hr>
<div class="row">
<div class="col-sm-4"> <div class="col-sm-4">
<div class="form-group"> <div class="form-group">
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ <label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5" min="0.5" required> <input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" min="0.5" required>
</div> </div>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<div class="form-group"> <div class="form-group">
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ <label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label> translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1" max="65535 " required> <input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5" min="0.5" required>
</div> </div>
</div> </div>
<div class="col-sm-12">
<div class="form-group"> <div class="col-sm-4">
<div id="accordion-1"> <div class="form-group">
<div class="card"> <label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
<div class="card-header p-2" id="Role-1"> translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" aria-controls="collapseRole-1"> <input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1" max="65535 " required>
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} </div>
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', </div>
data['lang']) }}</small> <div class="col-sm-12">
</p> <div class="form-group">
</div> <div id="accordion-1">
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent=""> <div class="card">
<div class="card-body scroll"> <div class="card-header p-2" id="Role-1">
<div class="form-group"> <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" aria-controls="collapseRole-1">
{% for r in data['roles'] %} <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp; {{ r['role_name'].capitalize() }}</label></span> <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
{% end %} data['lang']) }}</small>
</p>
</div>
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp; {{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'buildServer',
data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
}}</button>
</fieldset>
{% if not data["server_api"] %}
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
<p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: white;"></i>&nbsp;{{ translate('error', 'serverJars1', data['lang']) }}<a style="color: red;" ; href="https://status.craftycontrol.com/status/craftycontrol" target="_blank">&nbsp;{{ translate('error', 'craftyStatus', data['lang']) }}</a>
&nbsp;{{ translate('error', 'serverJars2', data['lang']) }}</p>
</div> </div>
{% end %}
</div>
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'buildServer',
data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
}}</button>
</form>
</p>
</div> </div>
</div> </div>
</form>
{% end %} {% end %}
</div> </div>

View File

@ -186,7 +186,11 @@
"terribleFailure": "What a Terrible Failure!", "terribleFailure": "What a Terrible Failure!",
"superError": "You must be a super user to complete this action.", "superError": "You must be a super user to complete this action.",
"fileError": "File type must be an image.", "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",
"serverJars1": "Server JARs API unreachable. Please check",
"craftyStatus": "Crafty's status page",
"serverJars2": "for the most up to date information.",
"cronFormat": "Invalid Cron format detected"
}, },
"footer": { "footer": {
"allRightsReserved": "All rights reserved", "allRightsReserved": "All rights reserved",
@ -209,6 +213,10 @@
"preparingLogs": " Please wait while we prepare your logs... We`ll send a notification when they`re ready. This may take a while for large deployments.", "preparingLogs": " Please wait while we prepare your logs... We`ll send a notification when they`re ready. This may take a while for large deployments.",
"supportLogs": "Support Logs" "supportLogs": "Support Logs"
}, },
"offline": {
"offline": "Offline",
"pleaseConnect": "Please connect to the internet to use Crafty."
},
"panelConfig": { "panelConfig": {
"adminControls": "Admin Controls", "adminControls": "Admin Controls",
"allowedServers": "Allowed Servers", "allowedServers": "Allowed Servers",
@ -574,7 +582,8 @@
"documentation": "Documentation", "documentation": "Documentation",
"navigation": "Navigation", "navigation": "Navigation",
"newServer": "Create New Server", "newServer": "Create New Server",
"servers": "Servers" "servers": "Servers",
"inApp": "In App Docs"
}, },
"userConfig": { "userConfig": {
"apiKey": "API Keys", "apiKey": "API Keys",

11
main.py
View File

@ -27,6 +27,17 @@ if helper.check_root():
time.sleep(5) time.sleep(5)
Console.critical("Crafty shutting down. Root/Admin access denied.") Console.critical("Crafty shutting down. Root/Admin access denied.")
sys.exit(0) sys.exit(0)
if not (sys.version_info.major == 3 and sys.version_info.minor >= 9):
Console.critical(
"Python version mismatch. Python "
f"{sys.version_info.major}.{sys.version_info.minor} detected."
)
Console.critical("Crafty requires Python 3.9 or above. Please upgrade python.")
time.sleep(5)
Console.critical("Crafty shutting down.")
time.sleep(3)
Console.info("Crafty stopped. Exiting...")
sys.exit(0)
# pylint: disable=wrong-import-position # pylint: disable=wrong-import-position
try: try:
from app.classes.models.base_model import database_proxy from app.classes.models.base_model import database_proxy

View File

@ -18,4 +18,4 @@ termcolor==1.1
tornado==6.0 tornado==6.0
tzlocal==4.0 tzlocal==4.0
jsonschema==4.5.1 jsonschema==4.5.1
orjson==3.6.7 orjson==3.8.12