mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into bugfix/project_root
This commit is contained in:
commit
5ffc241e0a
@ -28,7 +28,7 @@ docker-build-dev:
|
||||
docker version
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
|
||||
script:
|
||||
- |
|
||||
@ -45,6 +45,7 @@ docker-build-dev:
|
||||
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
|
||||
--build-arg "CRAFTY_VER=${VERSION}"
|
||||
--provenance false
|
||||
--tag "$CI_REGISTRY_IMAGE${tag}"
|
||||
--tag "arcadiatechnology/crafty-4${tag}"
|
||||
--platform linux/arm64/v8,linux/amd64
|
||||
@ -84,7 +85,7 @@ docker-build-prod:
|
||||
docker version
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
|
||||
script:
|
||||
- |
|
||||
@ -100,6 +101,7 @@ docker-build-prod:
|
||||
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
|
||||
--build-arg "CRAFTY_VER=${VERSION}"
|
||||
--provenance false
|
||||
--tag "$CI_REGISTRY_IMAGE:$VERSION"
|
||||
--tag "$CI_REGISTRY_IMAGE:latest"
|
||||
--tag "arcadiatechnology/crafty-4:$VERSION"
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,11 +1,17 @@
|
||||
# Changelog
|
||||
## --- [4.0.23] - 2023/TBD
|
||||
## --- [4.1.0] - 2023/TBD
|
||||
### New features
|
||||
TBD
|
||||
- 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))
|
||||
### Refactor
|
||||
- Frontend Ajax Refactor | Start using API to send Remote Comms to Server ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/565))
|
||||
### Bug fixes
|
||||
TBD
|
||||
- Fix pipelines failing to build from gitlab pre-defined variable deprecation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/582))
|
||||
- Fix incompatible buildx provenance meta, causing digest issues on GL/DH container registries ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/582))
|
||||
- Fix Auth'd servers in roles | Refine server ordering ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/574))
|
||||
- Fix import loop detection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/575))
|
||||
- Fix Cargo errors on Ubuntu 23.04 installs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/579))
|
||||
### Tweaks
|
||||
TBD
|
||||
- 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))
|
||||
### Lang
|
||||
TBD
|
||||
<br><br>
|
||||
|
@ -1,5 +1,5 @@
|
||||
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
|
||||
# Crafty Controller 4.0.23
|
||||
# Crafty Controller 4.1.0
|
||||
> Python based Control Panel for your Minecraft Server
|
||||
|
||||
## What is Crafty Controller?
|
||||
|
@ -253,6 +253,7 @@ class ServersController(metaclass=Singleton):
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers(user_id):
|
||||
server_ids = []
|
||||
server_data: t.List[t.Dict[str, t.Any]] = []
|
||||
user_roles = HelperUsers.user_role_query(user_id)
|
||||
for user in user_roles:
|
||||
@ -260,11 +261,13 @@ class ServersController(metaclass=Singleton):
|
||||
user.role_id
|
||||
)
|
||||
for role in role_servers:
|
||||
server_data.append(
|
||||
ServersController().get_server_instance_by_id(
|
||||
role.server_id.server_id
|
||||
if role.server_id.server_id not in server_ids:
|
||||
server_ids.append(role.server_id.server_id)
|
||||
server_data.append(
|
||||
ServersController().get_server_instance_by_id(
|
||||
role.server_id.server_id
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return server_data
|
||||
|
||||
@ -275,11 +278,10 @@ class ServersController(metaclass=Singleton):
|
||||
for role in roles_list:
|
||||
role_users = HelperUsers.get_users_from_role(role.role_id)
|
||||
for user_role in role_users:
|
||||
user_ids.add(user_role.user_id)
|
||||
user_ids.add(user_role.user_id.user_id)
|
||||
|
||||
for user_id in HelperUsers.get_super_user_list():
|
||||
user_ids.add(user_id)
|
||||
|
||||
return user_ids
|
||||
|
||||
def get_all_servers_stats(self):
|
||||
|
@ -89,6 +89,7 @@ class UsersController:
|
||||
},
|
||||
},
|
||||
"hints": {"type": "boolean"},
|
||||
"server_order": {"type": "string"},
|
||||
}
|
||||
|
||||
# **********************************************************************************
|
||||
|
@ -386,7 +386,7 @@ class HelperUsers:
|
||||
|
||||
@staticmethod
|
||||
def get_users_from_role(role_id):
|
||||
UserRoles.select().where(UserRoles.role_id == role_id).execute()
|
||||
return UserRoles.select().where(UserRoles.role_id == role_id).execute()
|
||||
|
||||
# **********************************************************************************
|
||||
# ApiKeys Methods
|
||||
|
@ -16,6 +16,7 @@ import zipfile
|
||||
import pathlib
|
||||
import ctypes
|
||||
import shutil
|
||||
import shlex
|
||||
import subprocess
|
||||
import itertools
|
||||
from datetime import datetime
|
||||
@ -147,6 +148,29 @@ class Helpers:
|
||||
logger.error(f"Unable to resolve remote bedrock download url! \n{e}")
|
||||
return False
|
||||
|
||||
def get_execution_java(self, value, execution_command):
|
||||
if self.is_os_windows():
|
||||
execution_list = shlex.split(execution_command, posix=False)
|
||||
else:
|
||||
execution_list = shlex.split(execution_command, posix=True)
|
||||
if (
|
||||
not any(value in path for path in self.find_java_installs())
|
||||
and value != "java"
|
||||
):
|
||||
return
|
||||
if value != "java":
|
||||
if self.is_os_windows():
|
||||
execution_list[0] = '"' + value + '/bin/java"'
|
||||
else:
|
||||
execution_list[0] = '"' + value + '"'
|
||||
else:
|
||||
execution_list[0] = "java"
|
||||
execution_command = ""
|
||||
for item in execution_list:
|
||||
execution_command += item + " "
|
||||
|
||||
return execution_command
|
||||
|
||||
def detect_java(self):
|
||||
if len(self.find_java_installs()) > 0:
|
||||
return True
|
||||
|
@ -421,6 +421,7 @@ class TasksManager:
|
||||
)
|
||||
for item in jobs:
|
||||
logger.info(f"JOB: {item}")
|
||||
return task.schedule_id
|
||||
|
||||
def remove_all_server_tasks(self, server_id):
|
||||
schedules = HelpersManagement.get_schedules_by_server(server_id)
|
||||
@ -450,7 +451,6 @@ class TasksManager:
|
||||
# created task a child of itself.
|
||||
if str(job_data.get("parent")) == str(sch_id):
|
||||
job_data["parent"] = None
|
||||
|
||||
HelpersManagement.update_scheduled_task(sch_id, job_data)
|
||||
|
||||
if not (
|
||||
|
@ -281,74 +281,7 @@ class AjaxHandler(BaseHandler):
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
|
||||
if page == "send_command":
|
||||
command = self.get_body_argument("command", default=None, strip=True)
|
||||
server_id = self.get_argument("id", None)
|
||||
|
||||
if server_id is None:
|
||||
logger.warning("Server ID not found in send_command ajax call")
|
||||
Console.warning("Server ID not found in send_command ajax call")
|
||||
|
||||
srv_obj = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
|
||||
if command == srv_obj.settings["stop_command"]:
|
||||
logger.info(
|
||||
"Stop command detected as terminal input - intercepting."
|
||||
+ f"Starting Crafty's stop process for server with id: {server_id}"
|
||||
)
|
||||
self.controller.management.send_command(
|
||||
exec_user["user_id"], server_id, self.get_remote_ip(), "stop_server"
|
||||
)
|
||||
command = None
|
||||
elif command == "restart":
|
||||
logger.info(
|
||||
"Restart command detected as terminal input - intercepting."
|
||||
+ f"Starting Crafty's stop process for server with id: {server_id}"
|
||||
)
|
||||
self.controller.management.send_command(
|
||||
exec_user["user_id"],
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
"restart_server",
|
||||
)
|
||||
command = None
|
||||
if command:
|
||||
if srv_obj.check_running():
|
||||
srv_obj.send_command(command)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Sent command to "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)} "
|
||||
f"terminal: {command}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
elif page == "send_order":
|
||||
self.controller.users.update_server_order(
|
||||
exec_user["user_id"], bleach.clean(self.get_argument("order"))
|
||||
)
|
||||
return
|
||||
|
||||
elif page == "backup_now":
|
||||
server_id = self.get_argument("id", None)
|
||||
if server_id is None:
|
||||
logger.error("Server ID is none. Canceling backup!")
|
||||
return
|
||||
|
||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
self.controller.management.add_to_audit_log_raw(
|
||||
self.controller.users.get_user_by_id(exec_user["user_id"])["username"],
|
||||
exec_user["user_id"],
|
||||
server_id,
|
||||
f"Backup now executed for server {server_id} ",
|
||||
source_ip=self.get_remote_ip(),
|
||||
)
|
||||
|
||||
server.backup_server()
|
||||
|
||||
elif page == "select_photo":
|
||||
if page == "select_photo":
|
||||
if exec_user["superuser"]:
|
||||
photo = urllib.parse.unquote(self.get_argument("photo", ""))
|
||||
opacity = self.get_argument("opacity", 100)
|
||||
@ -382,23 +315,6 @@ class AjaxHandler(BaseHandler):
|
||||
self.controller.cached_login = "login_1.jpg"
|
||||
return
|
||||
|
||||
elif page == "kill":
|
||||
if not permissions["Commands"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Commands")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
svr = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
try:
|
||||
svr.kill()
|
||||
time.sleep(5)
|
||||
svr.cleanup_server_object()
|
||||
svr.record_server_stats()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Could not find PID for requested termsig. Full error: {e}"
|
||||
)
|
||||
return
|
||||
elif page == "eula":
|
||||
server_id = self.get_argument("id", None)
|
||||
svr = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
@ -624,12 +540,6 @@ class AjaxHandler(BaseHandler):
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
if page == "del_task":
|
||||
if not permissions["Schedule"] in user_perms:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Tasks")
|
||||
else:
|
||||
sch_id = self.get_argument("schedule_id", "-404")
|
||||
self.tasks_manager.remove_job(sch_id)
|
||||
|
||||
if page == "del_backup":
|
||||
if not permissions["Backup"] in user_perms:
|
||||
@ -668,84 +578,6 @@ class AjaxHandler(BaseHandler):
|
||||
):
|
||||
os.remove(file_path)
|
||||
|
||||
elif page == "delete_server":
|
||||
if not permissions["Config"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
logger.info(
|
||||
f"Removing server from panel for server: "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)}"
|
||||
)
|
||||
|
||||
server_data = self.controller.servers.get_server_data(server_id)
|
||||
server_name = server_data["server_name"]
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, False)
|
||||
|
||||
elif page == "delete_server_files":
|
||||
if not permissions["Config"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
logger.info(
|
||||
f"Removing server and all associated files for server: "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)}"
|
||||
)
|
||||
|
||||
server_data = self.controller.servers.get_server_data(server_id)
|
||||
server_name = server_data["server_name"]
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
for server in self.controller.servers.failed_servers:
|
||||
if server["server_id"] == int(server_id):
|
||||
return
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, True)
|
||||
|
||||
elif page == "delete_unloaded_server":
|
||||
if not permissions["Config"] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument("id", None)
|
||||
logger.info(
|
||||
f"Removing server and all associated files for server: "
|
||||
f"{self.controller.servers.get_server_friendly_name(server_id)}"
|
||||
)
|
||||
|
||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
||||
server_name = server_data["server_name"]
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
for item in self.controller.servers.failed_servers[:]:
|
||||
if item["server_id"] == int(server_id):
|
||||
self.controller.servers.failed_servers.remove(item)
|
||||
self.controller.remove_unloaded_server(server_id)
|
||||
|
||||
def check_server_id(self, server_id, page_name):
|
||||
if server_id is None:
|
||||
logger.warning(
|
||||
|
@ -6,7 +6,6 @@ import typing as t
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
import shlex
|
||||
import urllib.parse
|
||||
import bleach
|
||||
import requests
|
||||
@ -17,7 +16,6 @@ from tornado import iostream
|
||||
# TZLocal is set as a hidden import on win pipeline
|
||||
from tzlocal import get_localzone
|
||||
from tzlocal.utils import ZoneInfoNotFoundError
|
||||
from croniter import croniter
|
||||
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
@ -256,7 +254,12 @@ class PanelHandler(BaseHandler):
|
||||
user_order = user_order["server_order"].split(",")
|
||||
page_servers = []
|
||||
server_ids = []
|
||||
|
||||
for server in defined_servers:
|
||||
server_ids.append(str(server.server_id))
|
||||
if str(server.server_id) not in user_order:
|
||||
# a little unorthodox, but this will cut out a loop.
|
||||
# adding servers to the user order that don't already exist there.
|
||||
user_order.append(str(server.server_id))
|
||||
for server_id in user_order[:]:
|
||||
for server in defined_servers[:]:
|
||||
if str(server.server_id) == str(server_id):
|
||||
@ -265,14 +268,7 @@ class PanelHandler(BaseHandler):
|
||||
)
|
||||
user_order.remove(server_id)
|
||||
defined_servers.remove(server)
|
||||
|
||||
for server in defined_servers:
|
||||
server_ids.append(str(server.server_id))
|
||||
if server not in page_servers:
|
||||
page_servers.append(
|
||||
DatabaseShortcuts.get_data_obj(server.server_object)
|
||||
)
|
||||
|
||||
break
|
||||
for server_id in user_order[:]:
|
||||
# remove IDs in list that user no longer has access to
|
||||
if str(server_id) not in server_ids:
|
||||
@ -452,6 +448,7 @@ class PanelHandler(BaseHandler):
|
||||
page_servers.append(server)
|
||||
un_used_servers.remove(server)
|
||||
user_order.remove(server_id)
|
||||
break
|
||||
# we only want to set these server stats values once.
|
||||
# We need to update the flag so it only hits that if once.
|
||||
flag += 1
|
||||
@ -1053,7 +1050,7 @@ class PanelHandler(BaseHandler):
|
||||
page_data["schedule"]["cron_string"] = ""
|
||||
page_data["schedule"]["delay"] = 0
|
||||
page_data["schedule"]["time"] = ""
|
||||
page_data["schedule"]["interval"] = ""
|
||||
page_data["schedule"]["interval"] = 1
|
||||
# we don't need to check difficulty here.
|
||||
# We'll just default to basic for new schedules
|
||||
page_data["schedule"]["difficulty"] = "basic"
|
||||
@ -1556,156 +1553,6 @@ class PanelHandler(BaseHandler):
|
||||
role = self.controller.roles.get_role(r)
|
||||
exec_user_role.add(role["role_name"])
|
||||
|
||||
if page == "server_detail":
|
||||
if not permissions[
|
||||
"Config"
|
||||
] in self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
):
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_name = self.get_argument("server_name", None)
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
shutdown_timeout = self.get_argument("shutdown_timeout", 60)
|
||||
if superuser:
|
||||
log_path = self.get_argument("log_path", "")
|
||||
if log_path:
|
||||
if Helpers.is_os_windows():
|
||||
log_path.replace(" ", "^ ")
|
||||
log_path = Helpers.wtol_path(log_path)
|
||||
if not self.helper.validate_traversal(server_obj.path, log_path):
|
||||
log_path = ""
|
||||
executable = self.get_argument("executable", None)
|
||||
execution_command = self.get_argument("execution_command", None)
|
||||
server_ip = self.get_argument("server_ip", None)
|
||||
server_port = self.get_argument("server_port", None)
|
||||
if int(server_port) < 1 or int(server_port) > 65535:
|
||||
self.redirect(
|
||||
"/panel/error?error=Constraint Error: "
|
||||
"Port must be greater than 0 and less than 65535"
|
||||
)
|
||||
return
|
||||
executable_update_url = self.get_argument("executable_update_url", "")
|
||||
show_status = int(float(self.get_argument("show_status", "0")))
|
||||
else:
|
||||
execution_command = server_obj.execution_command
|
||||
executable = server_obj.executable
|
||||
stop_command = self.get_argument("stop_command", None)
|
||||
auto_start_delay = self.get_argument("auto_start_delay", "10")
|
||||
auto_start = int(float(self.get_argument("auto_start", "0")))
|
||||
crash_detection = int(float(self.get_argument("crash_detection", "0")))
|
||||
logs_delete_after = int(float(self.get_argument("logs_delete_after", "0")))
|
||||
java_selection = self.get_argument("java_selection", None)
|
||||
# make sure there is no whitespace
|
||||
ignored_exits = self.get_argument("ignored_exits", "").replace(" ", "")
|
||||
# subpage = self.get_argument('subpage', None)
|
||||
|
||||
server_id = self.check_server_id()
|
||||
if server_id is None:
|
||||
return
|
||||
if java_selection:
|
||||
try:
|
||||
if self.helper.is_os_windows():
|
||||
execution_list = shlex.split(execution_command, posix=False)
|
||||
else:
|
||||
execution_list = shlex.split(execution_command, posix=True)
|
||||
except ValueError:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid execution command. Java path"
|
||||
" must be surrounded by quotes."
|
||||
" (Are you missing a closing quote?)"
|
||||
)
|
||||
if (
|
||||
not any(
|
||||
java_selection in path for path in Helpers.find_java_installs()
|
||||
)
|
||||
and java_selection != "java"
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Attack attempted."
|
||||
+ " A copy of this report is being sent to server owner."
|
||||
)
|
||||
self.controller.management.add_to_audit_log_raw(
|
||||
exec_user["username"],
|
||||
exec_user["user_id"],
|
||||
server_id,
|
||||
f"Attempted to send bad java path for {server_id}."
|
||||
+ " Possible attack. Act accordingly.",
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
return
|
||||
if java_selection != "java":
|
||||
if self.helper.is_os_windows():
|
||||
execution_list[0] = '"' + java_selection + '/bin/java"'
|
||||
else:
|
||||
execution_list[0] = '"' + java_selection + '"'
|
||||
else:
|
||||
execution_list[0] = "java"
|
||||
execution_command = ""
|
||||
for item in execution_list:
|
||||
execution_command += item + " "
|
||||
|
||||
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
|
||||
stale_executable = server_obj.executable
|
||||
# Compares old jar name to page data being passed.
|
||||
# If they are different we replace the executable name in the
|
||||
if str(stale_executable) != str(executable):
|
||||
execution_command = execution_command.replace(
|
||||
str(stale_executable), str(executable)
|
||||
)
|
||||
|
||||
server_obj.server_name = server_name
|
||||
server_obj.shutdown_timeout = shutdown_timeout
|
||||
if superuser:
|
||||
if Helpers.validate_traversal(
|
||||
self.helper.get_servers_root_dir(), server_obj.path
|
||||
):
|
||||
server_obj.log_path = log_path
|
||||
if Helpers.validate_traversal(
|
||||
self.helper.get_servers_root_dir(), executable
|
||||
):
|
||||
server_obj.executable = executable
|
||||
server_obj.execution_command = execution_command
|
||||
server_obj.server_ip = server_ip
|
||||
server_obj.server_port = server_port
|
||||
server_obj.executable_update_url = executable_update_url
|
||||
server_obj.show_status = show_status
|
||||
else:
|
||||
server_obj.log_path = server_obj.log_path
|
||||
server_obj.executable = server_obj.executable
|
||||
server_obj.execution_command = execution_command
|
||||
server_obj.server_ip = server_obj.server_ip
|
||||
server_obj.server_port = server_obj.server_port
|
||||
server_obj.executable_update_url = server_obj.executable_update_url
|
||||
server_obj.stop_command = stop_command
|
||||
server_obj.auto_start_delay = auto_start_delay
|
||||
server_obj.auto_start = auto_start
|
||||
server_obj.crash_detection = crash_detection
|
||||
server_obj.logs_delete_after = logs_delete_after
|
||||
server_obj.ignored_exits = ignored_exits
|
||||
failed = False
|
||||
for servers in self.controller.servers.failed_servers:
|
||||
if servers["server_id"] == int(server_id):
|
||||
failed = True
|
||||
if not failed:
|
||||
self.controller.servers.update_server(server_obj)
|
||||
else:
|
||||
self.controller.servers.update_unloaded_server(server_obj)
|
||||
self.controller.servers.init_all_servers()
|
||||
self.controller.servers.crash_detection(server_obj)
|
||||
|
||||
self.controller.servers.refresh_server_settings(server_id)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Edited server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=config")
|
||||
|
||||
if page == "server_backup":
|
||||
logger.debug(self.request.arguments)
|
||||
|
||||
@ -1802,336 +1649,6 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
self.redirect("/panel/config_json")
|
||||
|
||||
if page == "new_schedule":
|
||||
server_id = self.check_server_id()
|
||||
if not server_id:
|
||||
return
|
||||
|
||||
if (
|
||||
not permissions["Schedule"]
|
||||
in self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
and not superuser
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Unauthorized access: User not authorized"
|
||||
)
|
||||
return
|
||||
|
||||
difficulty = bleach.clean(self.get_argument("difficulty", None))
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
enabled = bleach.clean(self.get_argument("enabled", "0"))
|
||||
name = bleach.clean(self.get_argument("name", ""))
|
||||
if difficulty == "basic":
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
interval = bleach.clean(self.get_argument("interval", None))
|
||||
interval_type = bleach.clean(self.get_argument("interval_type", None))
|
||||
# only check for time if it's number of days
|
||||
if interval_type == "days":
|
||||
sch_time = bleach.clean(self.get_argument("time", None))
|
||||
if int(interval) > 30:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid argument."
|
||||
" Days must be 30 or fewer."
|
||||
)
|
||||
return
|
||||
if action == "command":
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
|
||||
elif difficulty == "reaction":
|
||||
interval_type = "reaction"
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
delay = bleach.clean(self.get_argument("delay", None))
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
if action == "command":
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
|
||||
else:
|
||||
interval_type = ""
|
||||
cron_string = bleach.clean(self.get_argument("cron", ""))
|
||||
if not croniter.is_valid(cron_string):
|
||||
self.redirect(
|
||||
"/panel/error?error=INVALID FORMAT: Invalid Cron Format."
|
||||
)
|
||||
return
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
if action == "command":
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
if bleach.clean(self.get_argument("enabled", "0")) == "1":
|
||||
enabled = True
|
||||
else:
|
||||
enabled = False
|
||||
if bleach.clean(self.get_argument("one_time", "0")) == "1":
|
||||
one_time = True
|
||||
else:
|
||||
one_time = False
|
||||
|
||||
if interval_type == "days":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"start_time": sch_time,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
elif difficulty == "reaction":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": "",
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"parent": parent,
|
||||
"delay": delay,
|
||||
}
|
||||
elif difficulty == "advanced":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": "",
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": cron_string,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
else:
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"enabled": enabled,
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "00:00",
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
|
||||
self.tasks_manager.schedule_job(job_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Edited server {server_id}: added scheduled job",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
|
||||
|
||||
if page == "edit_schedule":
|
||||
server_id = self.check_server_id()
|
||||
if not server_id:
|
||||
return
|
||||
|
||||
if (
|
||||
not permissions["Schedule"]
|
||||
in self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
)
|
||||
and not superuser
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Unauthorized access: User not authorized"
|
||||
)
|
||||
return
|
||||
|
||||
sch_id = self.get_argument("sch_id", None)
|
||||
if sch_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Schedule ID")
|
||||
|
||||
difficulty = bleach.clean(self.get_argument("difficulty", None))
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
enabled = bleach.clean(self.get_argument("enabled", "0"))
|
||||
name = bleach.clean(self.get_argument("name", ""))
|
||||
if difficulty == "basic":
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
interval = bleach.clean(self.get_argument("interval", None))
|
||||
interval_type = bleach.clean(self.get_argument("interval_type", None))
|
||||
# only check for time if it's number of days
|
||||
if interval_type == "days":
|
||||
sch_time = bleach.clean(self.get_argument("time", None))
|
||||
if int(interval) > 30:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid argument."
|
||||
" Days must be 30 or fewer."
|
||||
)
|
||||
return
|
||||
if action == "command":
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
elif difficulty == "reaction":
|
||||
interval_type = "reaction"
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
delay = bleach.clean(self.get_argument("delay", None))
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
if action == "command":
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
else:
|
||||
interval_type = ""
|
||||
cron_string = bleach.clean(self.get_argument("cron", ""))
|
||||
if not croniter.is_valid(cron_string):
|
||||
self.redirect(
|
||||
"/panel/error?error=INVALID FORMAT: Invalid Cron Format."
|
||||
)
|
||||
return
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
if action == "command":
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
command = "stop_server"
|
||||
elif action == "restart":
|
||||
command = "restart_server"
|
||||
elif action == "backup":
|
||||
command = "backup_server"
|
||||
if bleach.clean(self.get_argument("enabled", "0")) == "1":
|
||||
enabled = True
|
||||
else:
|
||||
enabled = False
|
||||
if bleach.clean(self.get_argument("one_time", "0")) == "1":
|
||||
one_time = True
|
||||
else:
|
||||
one_time = False
|
||||
|
||||
if interval_type == "days":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"start_time": sch_time,
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
"parent": None,
|
||||
"delay": 0,
|
||||
}
|
||||
elif difficulty == "advanced":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": "",
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": cron_string,
|
||||
"delay": "",
|
||||
"parent": "",
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
}
|
||||
elif difficulty == "reaction":
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": "",
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "",
|
||||
"command": command,
|
||||
"cron_string": "",
|
||||
"enabled": enabled,
|
||||
"one_time": one_time,
|
||||
"parent": parent,
|
||||
"delay": delay,
|
||||
}
|
||||
else:
|
||||
job_data = {
|
||||
"name": name,
|
||||
"server_id": server_id,
|
||||
"action": action,
|
||||
"interval_type": interval_type,
|
||||
"interval": interval,
|
||||
"command": command,
|
||||
"enabled": enabled,
|
||||
# We'll base every interval off of a midnight start time.
|
||||
"start_time": "00:00",
|
||||
"delay": "",
|
||||
"parent": "",
|
||||
"one_time": one_time,
|
||||
"cron_string": "",
|
||||
}
|
||||
self.tasks_manager.update_job(sch_id, job_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
exec_user["user_id"],
|
||||
f"Edited server {server_id}: updated schedule",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
|
||||
|
||||
elif page == "edit_user":
|
||||
if bleach.clean(self.get_argument("username", None)).lower() == "system":
|
||||
self.redirect(
|
||||
|
@ -50,12 +50,15 @@ class PublicHandler(BaseHandler):
|
||||
if page == "login":
|
||||
template = "public/login.html"
|
||||
|
||||
elif page == 404:
|
||||
elif page == "404":
|
||||
template = "public/404.html"
|
||||
|
||||
elif page == "error":
|
||||
template = "public/error.html"
|
||||
|
||||
elif page == "offline":
|
||||
template = "public/offline.html"
|
||||
|
||||
elif page == "logout":
|
||||
self.clear_cookie("token")
|
||||
# self.clear_cookie("user")
|
||||
|
@ -4,6 +4,36 @@ from peewee import DoesNotExist
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
modify_role_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
},
|
||||
"servers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_id": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string",
|
||||
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
|
||||
},
|
||||
},
|
||||
"required": ["server_id", "permissions"],
|
||||
},
|
||||
},
|
||||
"manager": {"type": ["integer", "null"]},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
}
|
||||
|
||||
basic_modify_role_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
@ -109,7 +139,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
|
||||
)
|
||||
|
||||
try:
|
||||
validate(data, modify_role_schema)
|
||||
if auth_data[4]["superuser"]:
|
||||
validate(data, modify_role_schema)
|
||||
else:
|
||||
validate(data, basic_modify_role_schema)
|
||||
except ValidationError as e:
|
||||
return self.finish_json(
|
||||
400,
|
||||
|
@ -13,20 +13,39 @@ server_patch_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_name": {"type": "string", "minLength": 1},
|
||||
"path": {"type": "string", "minLength": 1},
|
||||
"backup_path": {"type": "string"},
|
||||
"executable": {"type": "string"},
|
||||
"log_path": {"type": "string", "minLength": 1},
|
||||
"execution_command": {"type": "string", "minLength": 1},
|
||||
"java_selection": {"type": "string"},
|
||||
"auto_start": {"type": "boolean"},
|
||||
"auto_start_delay": {"type": "integer"},
|
||||
"auto_start_delay": {"type": "integer", "minimum": 0},
|
||||
"crash_detection": {"type": "boolean"},
|
||||
"stop_command": {"type": "string"},
|
||||
"executable_update_url": {"type": "string", "minLength": 1},
|
||||
"executable_update_url": {"type": "string"},
|
||||
"server_ip": {"type": "string", "minLength": 1},
|
||||
"server_port": {"type": "integer"},
|
||||
"logs_delete_after": {"type": "integer"},
|
||||
"type": {"type": "string", "minLength": 1},
|
||||
"shutdown_timeout": {"type": "integer", "minimum": 0},
|
||||
"logs_delete_after": {"type": "integer", "minimum": 0},
|
||||
"ignored_exits": {"type": "string"},
|
||||
"show_status": {"type": "boolean"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
}
|
||||
basic_server_patch_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_name": {"type": "string", "minLength": 1},
|
||||
"executable": {"type": "string"},
|
||||
"java_selection": {"type": "string"},
|
||||
"auto_start": {"type": "boolean"},
|
||||
"auto_start_delay": {"type": "integer", "minimum": 0},
|
||||
"crash_detection": {"type": "boolean"},
|
||||
"stop_command": {"type": "string"},
|
||||
"shutdown_timeout": {"type": "integer"},
|
||||
"logs_delete_after": {"type": "integer", "minimum": 0},
|
||||
"ignored_exits": {"type": "string"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
@ -63,7 +82,11 @@ class ApiServersServerIndexHandler(BaseApiHandler):
|
||||
)
|
||||
|
||||
try:
|
||||
validate(data, server_patch_schema)
|
||||
# prevent general users from becoming bad actors
|
||||
if auth_data[4]["superuser"]:
|
||||
validate(data, server_patch_schema)
|
||||
else:
|
||||
validate(data, basic_server_patch_schema)
|
||||
except ValidationError as e:
|
||||
return self.finish_json(
|
||||
400,
|
||||
@ -88,9 +111,24 @@ class ApiServersServerIndexHandler(BaseApiHandler):
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||
java_flag = False
|
||||
for key in data:
|
||||
# If we don't validate the input there could be security issues
|
||||
if key == "java_selection" and data[key] != "none":
|
||||
try:
|
||||
command = self.helper.get_execution_java(
|
||||
data[key], server_obj.execution_command
|
||||
)
|
||||
setattr(server_obj, "execution_command", command)
|
||||
except ValueError:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID EXECUTION COMMAND"}
|
||||
)
|
||||
java_flag = True
|
||||
|
||||
if key != "path":
|
||||
if key == "execution_command" and java_flag:
|
||||
continue
|
||||
setattr(server_obj, key, data[key])
|
||||
self.controller.servers.update_server(server_obj)
|
||||
|
||||
@ -134,7 +172,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
|
||||
)
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, remove_files)
|
||||
failed = False
|
||||
for item in self.controller.servers.failed_servers[:]:
|
||||
if item["server_id"] == int(server_id):
|
||||
self.controller.servers.failed_servers.remove(item)
|
||||
failed = True
|
||||
|
||||
if failed:
|
||||
self.controller.remove_unloaded_server(server_id)
|
||||
else:
|
||||
self.controller.remove_server(server_id, remove_files)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
|
@ -35,7 +35,13 @@ class ApiServersServerStdinHandler(BaseApiHandler):
|
||||
"Please report this to the devs"
|
||||
)
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
decoded = self.request.body.decode("utf-8")
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
f"Sent command ({decoded}) to terminal",
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip(),
|
||||
)
|
||||
if svr.send_command(self.request.body.decode("utf-8")):
|
||||
return self.finish_json(
|
||||
200,
|
||||
|
@ -1,16 +1,121 @@
|
||||
# TODO: create and read
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from croniter import croniter
|
||||
from jsonschema import ValidationError, validate
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
new_task_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
},
|
||||
"interval": {"type": "integer"},
|
||||
"interval_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
# Basic tasks
|
||||
"hours",
|
||||
"minutes",
|
||||
"days",
|
||||
# Chain reaction tasks:
|
||||
"reaction",
|
||||
# CRON tasks:
|
||||
"",
|
||||
],
|
||||
},
|
||||
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
|
||||
"command": {"type": ["string", "null"]},
|
||||
"one_time": {"type": "boolean", "default": False},
|
||||
"cron_string": {"type": "string", "default": ""},
|
||||
"parent": {"type": ["integer", "null"]},
|
||||
"delay": {"type": "integer", "default": 0},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
}
|
||||
|
||||
|
||||
class ApiServersServerTasksIndexHandler(BaseApiHandler):
|
||||
def get(self, server_id: str, task_id: str):
|
||||
pass
|
||||
|
||||
def post(self, server_id: str, task_id: str):
|
||||
pass
|
||||
def post(self, server_id: str):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
|
||||
try:
|
||||
data = json.loads(self.request.body)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||
)
|
||||
|
||||
try:
|
||||
validate(data, new_task_schema)
|
||||
except ValidationError as e:
|
||||
return self.finish_json(
|
||||
400,
|
||||
{
|
||||
"status": "error",
|
||||
"error": "INVALID_JSON_SCHEMA",
|
||||
"error_data": str(e),
|
||||
},
|
||||
)
|
||||
|
||||
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
|
||||
# if the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.SCHEDULE
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
auth_data[4]["user_id"], server_id
|
||||
)
|
||||
):
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
data["server_id"] = server_id
|
||||
if not data.get("start_time"):
|
||||
data["start_time"] = "00:00"
|
||||
|
||||
# validate cron string
|
||||
if data["cron_string"] != "" and not croniter.is_valid(data["cron_string"]):
|
||||
return self.finish_json(
|
||||
405,
|
||||
{
|
||||
"status": "error",
|
||||
"error": self.helper.translation.translate(
|
||||
"error",
|
||||
"cronFormat",
|
||||
self.controller.users.get_user_lang_by_id(
|
||||
auth_data[4]["user_id"]
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
if "parent" not in data:
|
||||
data["parent"] = None
|
||||
task_id = self.tasks_manager.schedule_job(data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
f"Edited server {server_id}: added schedule",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
|
||||
self.finish_json(200, {"status": "ok", "data": {"schedule_id": task_id}})
|
||||
|
@ -3,6 +3,7 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from croniter import croniter
|
||||
from jsonschema import ValidationError, validate
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
|
||||
@ -35,6 +36,7 @@ task_patch_schema = {
|
||||
"",
|
||||
],
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
|
||||
"command": {"type": ["string", "null"]},
|
||||
"one_time": {"type": "boolean", "default": False},
|
||||
@ -49,10 +51,47 @@ task_patch_schema = {
|
||||
|
||||
class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
|
||||
def get(self, server_id: str, task_id: str):
|
||||
pass
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.SCHEDULE
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
auth_data[4]["user_id"], server_id
|
||||
)
|
||||
):
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
self.finish_json(200, self.controller.management.get_scheduled_task(task_id))
|
||||
|
||||
def delete(self, server_id: str, task_id: str):
|
||||
pass
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.SCHEDULE
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
auth_data[4]["user_id"], server_id
|
||||
)
|
||||
):
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
try:
|
||||
self.tasks_manager.remove_job(task_id)
|
||||
except Exception:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "NO SCHEDULE FOUND"}
|
||||
)
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
f"Edited server {server_id}: removed schedule",
|
||||
server_id,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.tasks_manager.reload_schedule_from_db()
|
||||
|
||||
return self.finish_json(200, {"status": "ok"})
|
||||
|
||||
def patch(self, server_id: str, task_id: str):
|
||||
auth_data = self.authenticate_user()
|
||||
@ -96,6 +135,21 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
|
||||
if str(data.get("parent")) == str(task_id) and data.get("parent") is not None:
|
||||
data["parent"] = None
|
||||
|
||||
data["server_id"] = server_id
|
||||
if data["cron_string"] != "" and not croniter.is_valid(data["cron_string"]):
|
||||
return self.finish_json(
|
||||
405,
|
||||
{
|
||||
"status": "error",
|
||||
"error": self.helper.translation.translate(
|
||||
"error",
|
||||
"cronFormat",
|
||||
self.controller.users.get_user_lang_by_id(
|
||||
auth_data[4]["user_id"]
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
self.tasks_manager.update_job(task_id, data)
|
||||
|
||||
self.controller.management.add_to_audit_log(
|
||||
|
@ -333,7 +333,7 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
if import_type == "import_jar":
|
||||
if self.helper.is_subdir(
|
||||
import_server_path, self.controller.project_root
|
||||
self.controller.project_root, import_server_path
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Loop Error: The selected path will cause"
|
||||
@ -499,7 +499,7 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
if import_type == "import_jar":
|
||||
if self.helper.is_subdir(
|
||||
import_server_path, self.controller.project_root
|
||||
self.controller.project_root, import_server_path
|
||||
):
|
||||
self.redirect(
|
||||
"/panel/error?error=Loop Error: The selected path will cause"
|
||||
|
@ -11,6 +11,9 @@ except ModuleNotFoundError as e:
|
||||
|
||||
class CustomStaticHandler(tornado.web.StaticFileHandler):
|
||||
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:
|
||||
return super().validate_absolute_path(root, absolute_path)
|
||||
except tornado.web.HTTPError as error:
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": 23
|
||||
"minor": 1,
|
||||
"sub": 0
|
||||
}
|
||||
|
40
app/frontend/static/assets/crafty.webmanifest
Normal file
40
app/frontend/static/assets/crafty.webmanifest
Normal 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
|
||||
}
|
BIN
app/frontend/static/assets/images/Crafty_4-0.png
Normal file
BIN
app/frontend/static/assets/images/Crafty_4-0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
BIN
app/frontend/static/assets/images/crafty-logo-square-1024.png
Normal file
BIN
app/frontend/static/assets/images/crafty-logo-square-1024.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 424 KiB |
BIN
app/frontend/static/assets/images/crafty-logo-square-96.png
Normal file
BIN
app/frontend/static/assets/images/crafty-logo-square-96.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
46
app/frontend/static/assets/js/shared/service-worker.js
Normal file
46
app/frontend/static/assets/js/shared/service-worker.js
Normal 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;
|
||||
}
|
||||
})());
|
||||
}
|
||||
});
|
@ -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" />
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.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 -->
|
||||
|
||||
@ -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>
|
||||
|
||||
{% block js %}
|
||||
|
@ -14,6 +14,13 @@
|
||||
<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/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 -->
|
||||
<!-- Plugin css for this page -->
|
||||
<!-- End Plugin css for this page -->
|
||||
|
@ -647,10 +647,13 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
url: `/api/v2/servers/${server_id}/action/${command}`,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
if (command === "clone_server" && data.status === "ok") {
|
||||
window.location.reload();
|
||||
}
|
||||
/*setTimeout(function () {
|
||||
if (command != 'start_server') {
|
||||
location.reload();
|
||||
@ -705,24 +708,6 @@
|
||||
document.querySelector('.dynamicMsg').appendChild(parentEl);
|
||||
}
|
||||
|
||||
function send_kill(server_id) {
|
||||
/* this getCookie function is in base.html */
|
||||
const token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/kill?id=' + server_id,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
/*setTimeout(function () {
|
||||
location.reload();
|
||||
}, 10000);*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function update_one_server_status(server) {
|
||||
/* Mobile view update */
|
||||
server_cpu = document.getElementById('server_cpu_' + server.id);
|
||||
@ -901,17 +886,11 @@
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
send_kill(server_id);
|
||||
send_command(server_id, "kill_server");
|
||||
let dialog = bootbox.dialog({
|
||||
title: '{% raw translate("dashboard", "killing", data["lang"]) %}',
|
||||
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
|
||||
});
|
||||
|
||||
dialog.init(function () {
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 15000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1000,7 +979,13 @@
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
cloneServer(server_id);
|
||||
send_command(server_id, 'clone_server');
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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> {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script src="/static/assets/vendors/js/jquery-ui.js"></script>
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/jquery-ui.css">
|
||||
@ -1069,12 +1044,12 @@
|
||||
const token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
type: "PATCH",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/send_order?order=' + id_string,
|
||||
data: {
|
||||
order: id_string,
|
||||
},
|
||||
url: `/api/v2/users/@me`,
|
||||
data: JSON.stringify({
|
||||
server_order: id_string,
|
||||
}),
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
|
@ -12,6 +12,15 @@
|
||||
<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">
|
||||
|
||||
|
||||
<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 -->
|
||||
@ -24,7 +33,7 @@
|
||||
<style>
|
||||
.auth.auth-bg-1 {
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
@ -77,6 +86,21 @@
|
||||
<script src="/static/assets/js/shared/settings.js"></script>
|
||||
<script src="/static/assets/js/shared/todolist.js"></script>
|
||||
<!-- 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>
|
||||
|
||||
</html>
|
@ -321,9 +321,60 @@
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
function gather_server_json() {
|
||||
servers = [];
|
||||
for (s = 0; s < page_servers.length; s++){
|
||||
mask = ""
|
||||
for (i = 0; i < permissions.length; i++){
|
||||
if ($(`#permission_${page_servers[s].id}_${permissions[i]}`).prop('checked')){
|
||||
mask += "1"
|
||||
}else{
|
||||
mask += "0"
|
||||
}
|
||||
}
|
||||
servers.push(JSON.stringify({"id": page_servers[s].id, "permissions": mask}));
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
});
|
||||
const roleId = new URLSearchParams(document.location.search).get('id');
|
||||
|
||||
$("#config_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let configForm = document.getElementById("config_form");
|
||||
|
||||
let formData = new FormData(configForm);
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
let send_object = Object()
|
||||
send_object.servers = []
|
||||
send_object.name = formDataObject.role_name
|
||||
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
|
||||
let res = await fetch(`/api/v2/roles/${roleId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.reload();
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.error,
|
||||
message: responseData.error_data
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
@ -14,7 +14,8 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
|
||||
data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -76,10 +77,14 @@
|
||||
<li class="playerItem">
|
||||
<h3>{{ player }}</h3>
|
||||
<div class="buttons">
|
||||
<button onclick="send_command_to_server('ban {{ player }}')" type="button" class="btn btn-danger">Ban</button>
|
||||
<button onclick="send_command_to_server('kick {{ player }}')" type="button" class="btn btn-outline-danger">Kick</button>
|
||||
<button onclick="send_command_to_server('op {{ player }}')" type="button" class="btn btn-warning">OP</button>
|
||||
<button onclick="send_command_to_server('deop {{ player }}')" type="button" class="btn btn-outline-warning">De-OP</button>
|
||||
<button onclick="send_command_to_server('ban {{ player }}')" type="button"
|
||||
class="btn btn-danger">Ban</button>
|
||||
<button onclick="send_command_to_server('kick {{ player }}')" type="button"
|
||||
class="btn btn-outline-danger">Kick</button>
|
||||
<button onclick="send_command_to_server('op {{ player }}')" type="button"
|
||||
class="btn btn-warning">OP</button>
|
||||
<button onclick="send_command_to_server('deop {{ player }}')" type="button"
|
||||
class="btn btn-outline-warning">De-OP</button>
|
||||
</div>
|
||||
</li>
|
||||
{% end %}
|
||||
@ -136,21 +141,22 @@
|
||||
|
||||
});
|
||||
|
||||
function send_command_to_server(command) {
|
||||
async function send_command_to_server(command) {
|
||||
console.log(command)
|
||||
var token = getCookie("_xsrf")
|
||||
console.log('sending command: ' + command)
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/send_command?id=' + serverId,
|
||||
data: { command },
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: command,
|
||||
});
|
||||
|
||||
let responseData = await res.text();
|
||||
console.log("got response:");
|
||||
console.log(responseData);
|
||||
}
|
||||
|
||||
|
||||
|
@ -326,7 +326,7 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/backup_now?id=' + server_id,
|
||||
url: `/api/v2/servers/${server_id}/action/backup_server`,
|
||||
success: function (data) {
|
||||
return;
|
||||
},
|
||||
|
@ -43,10 +43,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<form class="forms-sample" method="post" id="config_form" action="/panel/server_detail">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
<form class="forms-sample" method="post" id="config_form">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small
|
||||
@ -96,7 +93,7 @@
|
||||
</label>
|
||||
<select class="form-select form-control form-control-lg select-css" id="java_selection"
|
||||
name="java_selection" form="config_form">
|
||||
<option value="">{{ translate('serverConfig',
|
||||
<option value="none">{{ translate('serverConfig',
|
||||
'javaNoChange', data['lang'])}}</option>
|
||||
{% for path in data['java_versions'] %}
|
||||
<option value="{{path}}">{{path}}</option>
|
||||
@ -359,7 +356,7 @@
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_server?id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}`,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
@ -373,7 +370,7 @@
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_server_files?id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}?files=true`,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
@ -393,7 +390,7 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}/action/${command}`,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
@ -522,7 +519,7 @@
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_unloaded_server?id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}`,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
@ -550,11 +547,92 @@
|
||||
$('.port-hint').popover("hide");
|
||||
});
|
||||
|
||||
async function postFormFieldsAsJson({ url, formData }) {
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject);
|
||||
|
||||
//Set the fetch options (headers, body)
|
||||
let fetchOptions = {
|
||||
//HTTP method set to POST.
|
||||
method: "PATCH",
|
||||
//Set the headers that specify you're sending a JSON body request and accepting JSON response
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
// POST request body as JSON string.
|
||||
body: formDataJsonString,
|
||||
};
|
||||
|
||||
//Get the response body as JSON.
|
||||
//If the response was not OK, throw an error.
|
||||
let res = await fetch(url, fetchOptions);
|
||||
|
||||
//If the response is not ok throw an error (for debugging)
|
||||
if (!res.ok) {
|
||||
let error = await res.text();
|
||||
throw new Error(error);
|
||||
}
|
||||
//If the response was OK, return the response body.
|
||||
return res.json();
|
||||
}
|
||||
function replacer(key, value) {
|
||||
if (key != "ignored_exits") {
|
||||
if (typeof value == "boolean" || key === "executable_update_url") {
|
||||
return value
|
||||
} else {
|
||||
return (isNaN(value) ? value : +value);
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
let token = getCookie("_xsrf")
|
||||
webSocket.on('remove_spinner', function () {
|
||||
document.getElementById("update-spinner").style.visibility = "hidden";
|
||||
});
|
||||
$("#config_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let configForm = document.getElementById("config_form");
|
||||
|
||||
let formData = new FormData(configForm);
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
//We need to make sure these are sent regardless of whether or not they're checked
|
||||
formDataObject.show_status = $("#show_status").prop('checked');
|
||||
formDataObject.crash_detection = $("#crash_detection").prop('checked');
|
||||
formDataObject.auto_start = $("#auto_start").prop('checked');
|
||||
console.log(formDataObject);
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
formDataJsonString["ignored_exits"] = toString(formDataJsonString["ignored_exits"]);
|
||||
console.log(formDataJsonString.ignored_exits)
|
||||
|
||||
console.log(formDataJsonString);
|
||||
|
||||
let res = await fetch(`/api/v2/servers/${serverId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.reload();
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.error,
|
||||
message: responseData.error_data
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -37,15 +37,12 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-sm-8">
|
||||
{% if data['new_schedule'] == True %}
|
||||
<form class="forms-sample" method="post"
|
||||
<form class="forms-sample" method="post" id="new_schedule_form"
|
||||
action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
|
||||
{% else %}
|
||||
<form class="forms-sample" method="post"
|
||||
<form class="forms-sample" method="post" id="schedule_form"
|
||||
action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
|
||||
{% end %}
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label>
|
||||
@ -89,7 +86,7 @@
|
||||
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' ,
|
||||
data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="interval" id="interval"
|
||||
value="{{ data['schedule']['interval'] }}" placeholder="Interval" required>
|
||||
value="{{ data['schedule']['interval'] }}" placeholder="Interval" required min="1">
|
||||
<br>
|
||||
<br>
|
||||
<select id="interval_type" onchange="ifDays(this);" name="interval_type"
|
||||
@ -108,7 +105,7 @@
|
||||
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' ,
|
||||
data['lang']) }}</small> </label>
|
||||
<input type="time" class="form-control" name="time" id="time"
|
||||
<input type="time" class="form-control" name="start_time" id="time"
|
||||
value="{{ data['schedule']['time'] }}" placeholder="Time" required>
|
||||
</div>
|
||||
</div>
|
||||
@ -127,7 +124,7 @@
|
||||
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="input" class="form-control" name="cron" id="cron"
|
||||
<input type="input" class="form-control" name="cron_string" id="cron"
|
||||
value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
|
||||
</div>
|
||||
</div>
|
||||
@ -234,8 +231,120 @@
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
function replacer(key, value) {
|
||||
if (key != "start_time" && key != "cron_string" && key != "interval_type") {
|
||||
if (typeof value == "boolean") {
|
||||
return value
|
||||
}
|
||||
console.log(key)
|
||||
if (key === "interval" && value === ""){
|
||||
return 0;
|
||||
}
|
||||
if (key === "command" && typeof(value === "integer")){
|
||||
return value.toString();
|
||||
}else {
|
||||
return (isNaN(value) ? value : +value);
|
||||
}
|
||||
} else {
|
||||
if (value === "" && key == "start_time"){
|
||||
return "00:00";
|
||||
}else{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverId = new URLSearchParams(document.location.search).get('id');
|
||||
const schId = new URLSearchParams(document.location.search).get('sch_id');
|
||||
$(document).ready(function () {
|
||||
console.log("ready!");
|
||||
$("#new_schedule_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let schForm = document.getElementById("new_schedule_form");
|
||||
|
||||
let formData = new FormData(schForm);
|
||||
formData.delete("difficulty");
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
//We need to make sure these are sent regardless of whether or not they're checked
|
||||
formDataObject.enabled = $("#enabled").prop('checked');
|
||||
formDataObject.one_time = $("#one_time").prop('checked');
|
||||
if ($("#difficulty").val() == "reaction"){
|
||||
formDataObject.interval_type = "reaction";
|
||||
}
|
||||
if (formDataObject.cron_string != ""){
|
||||
formDataObject.interval_type = '';
|
||||
}
|
||||
console.log(formDataObject);
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
|
||||
console.log(formDataJsonString);
|
||||
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/tasks/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.href = `/panel/server_detail?id=${serverId}&subpage=schedules`;
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.status,
|
||||
message: responseData.error
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#schedule_form").on("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var token = getCookie("_xsrf")
|
||||
let schForm = document.getElementById("schedule_form");
|
||||
|
||||
let formData = new FormData(schForm);
|
||||
formData.delete("difficulty");
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
//We need to make sure these are sent regardless of whether or not they're checked
|
||||
formDataObject.enabled = $("#enabled").prop('checked');
|
||||
formDataObject.one_time = $("#one_time").prop('checked');
|
||||
if ($("#difficulty").val() == "reaction"){
|
||||
formDataObject.interval_type = "reaction";
|
||||
}
|
||||
if (formDataObject.cron_string != ""){
|
||||
formDataObject.interval_type = '';
|
||||
}
|
||||
console.log(formDataObject);
|
||||
// Format the plain form data as JSON
|
||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||
|
||||
console.log(formDataJsonString);
|
||||
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/tasks/${schId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-XSRFToken': token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: formDataJsonString,
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
window.location.href = `/panel/server_detail?id=${serverId}&subpage=schedules`;
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.error,
|
||||
message: responseData.error_data
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -265,6 +374,7 @@
|
||||
document.getElementById("parent").required = true;
|
||||
document.getElementById("interval").required = false;
|
||||
document.getElementById("time").required = false;
|
||||
$("#cron").val("");
|
||||
}
|
||||
else {
|
||||
document.getElementById("ifAdvanced").style.display = "none";
|
||||
@ -274,6 +384,7 @@
|
||||
document.getElementById("parent").required = false;
|
||||
document.getElementById("interval").required = true;
|
||||
document.getElementById("time").required = true;
|
||||
$("#cron").val("");
|
||||
}
|
||||
}
|
||||
function ifDays() {
|
||||
@ -286,22 +397,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function del_task(sch_id, id) {
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/del_task?server_id=' + id + '&schedule_id=' + sch_id,
|
||||
data: {
|
||||
schedule_id: sch_id,
|
||||
id: id
|
||||
},
|
||||
success: function (data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
function startup() {
|
||||
try {
|
||||
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);
|
||||
|
@ -90,7 +90,7 @@
|
||||
<p>{{schedule.command}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.interval}}" class="action">
|
||||
{% if schedule.interval != '' %}
|
||||
{% if schedule.interval_type != '' and schedule.interval_type != 'reaction' %}
|
||||
<p>{{ translate('serverSchedules', 'every', data['lang']) }}</p>
|
||||
<p>{{schedule.interval}} {{schedule.interval_type}}</p>
|
||||
{% elif schedule.interval_type == 'reaction' %}
|
||||
@ -440,21 +440,19 @@
|
||||
});
|
||||
});
|
||||
|
||||
function del_task(sch_id, id) {
|
||||
async function del_task(sch_id, id) {
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/del_task?server_id=' + id + '&schedule_id=' + sch_id,
|
||||
data: {
|
||||
schedule_id: sch_id,
|
||||
id: id
|
||||
},
|
||||
success: function (data) {
|
||||
location.reload();
|
||||
let res = await fetch(`/api/v2/servers/${id}/tasks/${sch_id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'token': token,
|
||||
},
|
||||
});
|
||||
let responseData = await res;
|
||||
if (responseData.statusText === "OK") {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -179,7 +179,7 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + serverId,
|
||||
url: `/api/v2/servers/${serverId}/action/${command}`,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
@ -311,12 +311,12 @@
|
||||
formdata.append('command', serverCommand)
|
||||
|
||||
console.log('sending command: ' + serverCommand)
|
||||
let res = await fetch("/ajax/send_command?id=" + serverId, {
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: formdata,
|
||||
body: serverCommand,
|
||||
});
|
||||
|
||||
let responseData = await res.text();
|
||||
|
@ -12,6 +12,14 @@
|
||||
<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 -->
|
||||
@ -24,7 +32,7 @@
|
||||
<style>
|
||||
.auth.auth-bg-1 {
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
@ -12,6 +12,14 @@
|
||||
<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 -->
|
||||
@ -24,7 +32,7 @@
|
||||
<style>
|
||||
.auth.auth-bg-1 {
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
@ -12,6 +12,13 @@
|
||||
<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 Controller 4"> -->
|
||||
<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 -->
|
||||
@ -24,7 +31,7 @@
|
||||
<style>
|
||||
.auth.auth-bg-1 {
|
||||
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-position: center;
|
||||
}
|
||||
@ -140,7 +147,15 @@
|
||||
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>
|
||||
|
||||
|
85
app/frontend/templates/public/offline.html
Normal file
85
app/frontend/templates/public/offline.html
Normal 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>
|
@ -10,7 +10,7 @@
|
||||
<style>
|
||||
.auth.auth-bg-1 {
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
@ -89,7 +89,7 @@
|
||||
</div>
|
||||
<!-- View for Small screen -->
|
||||
<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%;'>
|
||||
<hr />
|
||||
{% if data['running'] != 0 %}
|
||||
|
@ -14,6 +14,13 @@
|
||||
<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/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 -->
|
||||
<!-- Plugin css for this page -->
|
||||
<!-- End Plugin css for this page -->
|
||||
@ -98,7 +105,7 @@
|
||||
usingWebSockets = false;
|
||||
}
|
||||
// {% else %}
|
||||
let usingWebSockets = false;
|
||||
usingWebSockets = false;
|
||||
warn('WebSockets are not supported in Crafty if not using the https protocol')
|
||||
var webSocket;
|
||||
// {% end%}
|
||||
@ -106,6 +113,21 @@
|
||||
</script>
|
||||
{% block js %}
|
||||
<!-- 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 %}
|
||||
|
||||
|
@ -186,7 +186,8 @@
|
||||
"terribleFailure": "What a Terrible Failure!",
|
||||
"superError": "You must be a super user to complete this action.",
|
||||
"fileError": "File type must be an image.",
|
||||
"migration": "Crafty's main server storage is being mirgated to a new location. All server starts have been suspended during this time. Please wait while we finish this migration"
|
||||
"migration": "Crafty's main server storage is being mirgated to a new location. All server starts have been suspended during this time. Please wait while we finish this migration",
|
||||
"cronFormat": "Invalid Cron format detected"
|
||||
},
|
||||
"footer": {
|
||||
"allRightsReserved": "All rights reserved",
|
||||
@ -209,6 +210,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.",
|
||||
"supportLogs": "Support Logs"
|
||||
},
|
||||
"offline": {
|
||||
"offline": "Offline",
|
||||
"pleaseConnect": "Please connect to the internet to use Crafty."
|
||||
},
|
||||
"panelConfig": {
|
||||
"adminControls": "Admin Controls",
|
||||
"allowedServers": "Allowed Servers",
|
||||
@ -614,4 +619,4 @@
|
||||
"manager": "Manager",
|
||||
"selectManager": "Select Manager for User"
|
||||
}
|
||||
}
|
||||
}
|
11
main.py
11
main.py
@ -27,6 +27,17 @@ if helper.check_root():
|
||||
time.sleep(5)
|
||||
Console.critical("Crafty shutting down. Root/Admin access denied.")
|
||||
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
|
||||
try:
|
||||
from app.classes.models.base_model import database_proxy
|
||||
|
@ -18,4 +18,4 @@ termcolor==1.1
|
||||
tornado==6.0
|
||||
tzlocal==4.0
|
||||
jsonschema==4.5.1
|
||||
orjson==3.6.7
|
||||
orjson==3.8.12
|
||||
|
Loading…
Reference in New Issue
Block a user