mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into feature/discord-webhooks
This commit is contained in:
commit
096de18c2c
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,11 +1,18 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## --- [4.1.4] - 2023/TBD
|
## --- [4.2.0] - 2023/TBD
|
||||||
### New features
|
### New features
|
||||||
TBD
|
- Finish and Activate Arcadia notification backend ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/621))
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
TBD
|
- PWA: Removed the custom offline page in favour of browser default ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/607))
|
||||||
|
- Fix hidden servers appearing visible on public mobile status page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/612))
|
||||||
|
- Correctly handle if a server returns a string instead of json data on socket ping ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/614))
|
||||||
|
- Bump tornado to resolve #269 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/623))
|
||||||
|
- Bump crypto to resolve #267 & #268 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/622))
|
||||||
|
### Refactor
|
||||||
|
- Consolidate remaining frontend functions into API V2, and remove ajax internal API ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/585))
|
||||||
### Tweaks
|
### Tweaks
|
||||||
TBD
|
- Polish/Enhance display for InApp Documentation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/613))
|
||||||
|
- Add get_users command to Crafty's console ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/620))
|
||||||
### Lang
|
### Lang
|
||||||
TBD
|
TBD
|
||||||
<br><br>
|
<br><br>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
|
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
|
||||||
# Crafty Controller 4.1.4
|
# Crafty Controller 4.2.0
|
||||||
> Python based Control Panel for your Minecraft Server
|
> Python based Control Panel for your Minecraft Server
|
||||||
|
|
||||||
## What is Crafty Controller?
|
## What is Crafty Controller?
|
||||||
|
@ -79,8 +79,8 @@ class ManagementController:
|
|||||||
# Audit_Log Methods
|
# Audit_Log Methods
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_actity_log():
|
def get_activity_log():
|
||||||
return HelpersManagement.get_actity_log()
|
return HelpersManagement.get_activity_log()
|
||||||
|
|
||||||
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
|
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
|
||||||
return self.management_helper.add_to_audit_log(
|
return self.management_helper.add_to_audit_log(
|
||||||
|
@ -31,7 +31,7 @@ class UsersController:
|
|||||||
for permission in PermissionsCrafty.get_permissions_list()
|
for permission in PermissionsCrafty.get_permissions_list()
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"quantity": {"type": "number", "minimum": 0},
|
"quantity": {"type": "number", "minimum": -1},
|
||||||
"enabled": {"type": "boolean"},
|
"enabled": {"type": "boolean"},
|
||||||
}
|
}
|
||||||
self.user_jsonschema_props: t.Final = {
|
self.user_jsonschema_props: t.Final = {
|
||||||
@ -46,7 +46,7 @@ class UsersController:
|
|||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"maxLength": 20,
|
"maxLength": 20,
|
||||||
"minLength": 4,
|
"minLength": 6,
|
||||||
"examples": ["crafty"],
|
"examples": ["crafty"],
|
||||||
"title": "Password",
|
"title": "Password",
|
||||||
},
|
},
|
||||||
@ -73,6 +73,8 @@ class UsersController:
|
|||||||
"examples": [False],
|
"examples": [False],
|
||||||
"title": "Superuser",
|
"title": "Superuser",
|
||||||
},
|
},
|
||||||
|
"manager": {"type": ["integer", "null"]},
|
||||||
|
"theme": {"type": "string"},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@ -84,7 +86,7 @@ class UsersController:
|
|||||||
"roles": {
|
"roles": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "integer",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -16,6 +16,12 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
|
if isinstance(data, str):
|
||||||
|
logger.error(
|
||||||
|
"Failed to calculate stats. Expected object. "
|
||||||
|
f"Server returned string: {data}"
|
||||||
|
)
|
||||||
|
return
|
||||||
self.description = data.get("description")
|
self.description = data.get("description")
|
||||||
# print(self.description)
|
# print(self.description)
|
||||||
if isinstance(self.description, dict):
|
if isinstance(self.description, dict):
|
||||||
|
@ -148,7 +148,7 @@ class HelpersManagement:
|
|||||||
# Audit_Log Methods
|
# Audit_Log Methods
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_actity_log():
|
def get_activity_log():
|
||||||
query = AuditLog.select()
|
query = AuditLog.select()
|
||||||
return DatabaseShortcuts.return_db_rows(query)
|
return DatabaseShortcuts.return_db_rows(query)
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ class Users(BaseModel):
|
|||||||
manager = IntegerField(default=None, null=True)
|
manager = IntegerField(default=None, null=True)
|
||||||
pfp = CharField(default="/static/assets/images/faces-clipart/pic-3.png")
|
pfp = CharField(default="/static/assets/images/faces-clipart/pic-3.png")
|
||||||
theme = CharField(default="default")
|
theme = CharField(default="default")
|
||||||
|
cleared_notifs = CharField(default="default")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table_name = "users"
|
table_name = "users"
|
||||||
@ -171,6 +172,7 @@ class HelperUsers:
|
|||||||
"roles": [],
|
"roles": [],
|
||||||
"servers": [],
|
"servers": [],
|
||||||
"support_logs": "",
|
"support_logs": "",
|
||||||
|
"cleared_notifs": "",
|
||||||
}
|
}
|
||||||
user = model_to_dict(Users.get(Users.user_id == user_id))
|
user = model_to_dict(Users.get(Users.user_id == user_id))
|
||||||
|
|
||||||
|
@ -92,6 +92,9 @@ class MainPrompt(cmd.Cmd):
|
|||||||
|
|
||||||
self.controller.users.update_user(user_id, {"password": new_pass})
|
self.controller.users.update_user(user_id, {"password": new_pass})
|
||||||
|
|
||||||
|
def do_get_users(self, _line):
|
||||||
|
Console.info(self.controller.users.get_all_usernames())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def do_threads(_line):
|
def do_threads(_line):
|
||||||
for thread in threading.enumerate():
|
for thread in threading.enumerate():
|
||||||
|
@ -325,3 +325,12 @@ class FileHelpers:
|
|||||||
else:
|
else:
|
||||||
return "false"
|
return "false"
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def unzip_server(self, zip_path, user_id):
|
||||||
|
if Helpers.check_file_perms(zip_path):
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||||
|
# extracts archive to temp directory
|
||||||
|
zip_ref.extractall(temp_dir)
|
||||||
|
if user_id:
|
||||||
|
return temp_dir
|
||||||
|
@ -579,20 +579,16 @@ class Helpers:
|
|||||||
|
|
||||||
return version_data
|
return version_data
|
||||||
|
|
||||||
@staticmethod
|
def get_announcements(self):
|
||||||
def get_announcements():
|
data = []
|
||||||
data = (
|
|
||||||
'[{"id":"1","date":"Unknown",'
|
|
||||||
'"title":"Error getting Announcements",'
|
|
||||||
'"desc":"Error getting Announcements","link":""}]'
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get("https://craftycontrol.com/notify.json", timeout=2)
|
response = requests.get("https://craftycontrol.com/notify", timeout=2)
|
||||||
data = json.loads(response.content)
|
data = json.loads(response.content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to fetch notifications with error: {e}")
|
logger.error(f"Failed to fetch notifications with error: {e}")
|
||||||
|
|
||||||
|
if self.update_available:
|
||||||
|
data.append(self.update_available)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_version_string(self):
|
def get_version_string(self):
|
||||||
@ -1092,87 +1088,6 @@ class Helpers:
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def generate_tree(self, folder, output=""):
|
|
||||||
dir_list = []
|
|
||||||
unsorted_files = []
|
|
||||||
file_list = os.listdir(folder)
|
|
||||||
for item in file_list:
|
|
||||||
if os.path.isdir(os.path.join(folder, item)):
|
|
||||||
dir_list.append(item)
|
|
||||||
elif str(item) != self.ignored_names:
|
|
||||||
unsorted_files.append(item)
|
|
||||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
|
||||||
unsorted_files, key=str.casefold
|
|
||||||
)
|
|
||||||
for raw_filename in file_list:
|
|
||||||
filename = html.escape(raw_filename)
|
|
||||||
rel = os.path.join(folder, raw_filename)
|
|
||||||
dpath = os.path.join(folder, filename)
|
|
||||||
if os.path.isdir(rel):
|
|
||||||
if filename not in self.ignored_names:
|
|
||||||
output += f"""<li id="{dpath}li" class="tree-item"
|
|
||||||
data-path="{dpath}">
|
|
||||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
|
||||||
class="tree-caret tree-ctx-item tree-folder">
|
|
||||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
|
||||||
data-name="{filename}" onclick="getDirView(event)">
|
|
||||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
|
||||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
|
||||||
{filename}
|
|
||||||
</span>
|
|
||||||
</div><li>
|
|
||||||
\n"""
|
|
||||||
else:
|
|
||||||
if filename not in self.ignored_names:
|
|
||||||
output += f"""<li id="{dpath}li"
|
|
||||||
class="d-block tree-ctx-item tree-file tree-item"
|
|
||||||
data-path="{dpath}"
|
|
||||||
data-name="{filename}"
|
|
||||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;">
|
|
||||||
<i class="far fa-file"></i></span>{filename}</li>"""
|
|
||||||
return output
|
|
||||||
|
|
||||||
def generate_dir(self, folder, output=""):
|
|
||||||
dir_list = []
|
|
||||||
unsorted_files = []
|
|
||||||
file_list = os.listdir(folder)
|
|
||||||
for item in file_list:
|
|
||||||
if os.path.isdir(os.path.join(folder, item)):
|
|
||||||
dir_list.append(item)
|
|
||||||
elif str(item) != self.ignored_names:
|
|
||||||
unsorted_files.append(item)
|
|
||||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
|
||||||
unsorted_files, key=str.casefold
|
|
||||||
)
|
|
||||||
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
|
|
||||||
for raw_filename in file_list:
|
|
||||||
filename = html.escape(raw_filename)
|
|
||||||
dpath = os.path.join(folder, filename)
|
|
||||||
rel = os.path.join(folder, raw_filename)
|
|
||||||
if os.path.isdir(rel):
|
|
||||||
if filename not in self.ignored_names:
|
|
||||||
output += f"""<li id="{dpath}li" class="tree-item"
|
|
||||||
data-path="{dpath}">
|
|
||||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
|
||||||
class="tree-caret tree-ctx-item tree-folder">
|
|
||||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
|
||||||
data-name="{filename}" onclick="getDirView(event)">
|
|
||||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
|
||||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
|
||||||
{filename}
|
|
||||||
</span>
|
|
||||||
</div><li>"""
|
|
||||||
else:
|
|
||||||
if filename not in self.ignored_names:
|
|
||||||
output += f"""<li id="{dpath}li"
|
|
||||||
class="d-block tree-ctx-item tree-file tree-item"
|
|
||||||
data-path="{dpath}"
|
|
||||||
data-name="{filename}"
|
|
||||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;">
|
|
||||||
<i class="far fa-file"></i></span>{filename}</li>"""
|
|
||||||
output += "</ul>\n"
|
|
||||||
return output
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_zip_tree(folder, output=""):
|
def generate_zip_tree(folder, output=""):
|
||||||
file_list = os.listdir(folder)
|
file_list = os.listdir(folder)
|
||||||
@ -1216,23 +1131,6 @@ class Helpers:
|
|||||||
</input></div><li>"""
|
</input></div><li>"""
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def unzip_server(self, zip_path, user_id):
|
|
||||||
if Helpers.check_file_perms(zip_path):
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
|
||||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
||||||
# extracts archive to temp directory
|
|
||||||
zip_ref.extractall(temp_dir)
|
|
||||||
if user_id:
|
|
||||||
self.websocket_helper.broadcast_user(
|
|
||||||
user_id, "send_temp_path", {"path": temp_dir}
|
|
||||||
)
|
|
||||||
|
|
||||||
def backup_select(self, path, user_id):
|
|
||||||
if user_id:
|
|
||||||
self.websocket_helper.broadcast_user(
|
|
||||||
user_id, "send_temp_path", {"path": path}
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unzip_backup_archive(backup_path, zip_name):
|
def unzip_backup_archive(backup_path, zip_name):
|
||||||
zip_path = os.path.join(backup_path, zip_name)
|
zip_path = os.path.join(backup_path, zip_name)
|
||||||
|
@ -5,6 +5,7 @@ from datetime import datetime
|
|||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from peewee import DoesNotExist
|
from peewee import DoesNotExist
|
||||||
@ -84,6 +85,17 @@ class Controller:
|
|||||||
def set_project_root(self, root_dir):
|
def set_project_root(self, root_dir):
|
||||||
self.project_root = root_dir
|
self.project_root = root_dir
|
||||||
|
|
||||||
|
def set_config_json(self, data):
|
||||||
|
current_config = self.helper.get_all_settings()
|
||||||
|
for key in current_config:
|
||||||
|
if key in data:
|
||||||
|
current_config[key] = data[key]
|
||||||
|
keys = list(current_config.keys())
|
||||||
|
keys.sort()
|
||||||
|
sorted_data = {i: current_config[i] for i in keys}
|
||||||
|
with open(self.helper.settings_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(sorted_data, f, indent=4)
|
||||||
|
|
||||||
def package_support_logs(self, exec_user):
|
def package_support_logs(self, exec_user):
|
||||||
if exec_user["preparing"]:
|
if exec_user["preparing"]:
|
||||||
return
|
return
|
||||||
@ -300,15 +312,6 @@ class Controller:
|
|||||||
Helpers.ensure_dir_exists(new_server_path)
|
Helpers.ensure_dir_exists(new_server_path)
|
||||||
Helpers.ensure_dir_exists(backup_path)
|
Helpers.ensure_dir_exists(backup_path)
|
||||||
|
|
||||||
def _copy_import_dir_files(existing_server_path):
|
|
||||||
existing_server_path = Helpers.get_os_understandable_path(
|
|
||||||
existing_server_path
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
FileHelpers.copy_dir(existing_server_path, new_server_path, True)
|
|
||||||
except shutil.Error as ex:
|
|
||||||
logger.error(f"Server import failed with error: {ex}")
|
|
||||||
|
|
||||||
def _create_server_properties_if_needed(port, empty=False):
|
def _create_server_properties_if_needed(port, empty=False):
|
||||||
properties_file = os.path.join(new_server_path, "server.properties")
|
properties_file = os.path.join(new_server_path, "server.properties")
|
||||||
has_properties = os.path.exists(properties_file)
|
has_properties = os.path.exists(properties_file)
|
||||||
@ -336,22 +339,25 @@ class Controller:
|
|||||||
server_file = f"{create_data['type']}-{create_data['version']}.jar"
|
server_file = f"{create_data['type']}-{create_data['version']}.jar"
|
||||||
|
|
||||||
# Create an EULA file
|
# Create an EULA file
|
||||||
with open(
|
if "agree_to_eula" in create_data:
|
||||||
os.path.join(new_server_path, "eula.txt"), "w", encoding="utf-8"
|
with open(
|
||||||
) as file:
|
os.path.join(new_server_path, "eula.txt"), "w", encoding="utf-8"
|
||||||
file.write(
|
) as file:
|
||||||
"eula=" + ("true" if create_data["agree_to_eula"] else "false")
|
file.write(
|
||||||
)
|
"eula="
|
||||||
|
+ ("true" if create_data["agree_to_eula"] else "false")
|
||||||
|
)
|
||||||
elif root_create_data["create_type"] == "import_server":
|
elif root_create_data["create_type"] == "import_server":
|
||||||
_copy_import_dir_files(create_data["existing_server_path"])
|
|
||||||
server_file = create_data["jarfile"]
|
server_file = create_data["jarfile"]
|
||||||
elif root_create_data["create_type"] == "import_zip":
|
elif root_create_data["create_type"] == "import_zip":
|
||||||
# TODO: Copy files from the zip file to the new server directory
|
# TODO: Copy files from the zip file to the new server directory
|
||||||
server_file = create_data["jarfile"]
|
server_file = create_data["jarfile"]
|
||||||
raise NotImplementedError("Not yet implemented")
|
raise NotImplementedError("Not yet implemented")
|
||||||
_create_server_properties_if_needed(
|
# self.import_helper.import_java_zip_server()
|
||||||
create_data["server_properties_port"],
|
if data["create_type"] == "minecraft_java":
|
||||||
)
|
_create_server_properties_if_needed(
|
||||||
|
create_data["server_properties_port"],
|
||||||
|
)
|
||||||
|
|
||||||
min_mem = create_data["mem_min"]
|
min_mem = create_data["mem_min"]
|
||||||
max_mem = create_data["mem_max"]
|
max_mem = create_data["mem_max"]
|
||||||
@ -364,30 +370,72 @@ class Controller:
|
|||||||
def _wrap_jar_if_windows():
|
def _wrap_jar_if_windows():
|
||||||
return f'"{server_file}"' if Helpers.is_os_windows() else server_file
|
return f'"{server_file}"' if Helpers.is_os_windows() else server_file
|
||||||
|
|
||||||
server_command = (
|
if root_create_data["create_type"] == "download_jar":
|
||||||
f"java -Xms{_gibs_to_mibs(min_mem)}M "
|
if Helpers.is_os_windows():
|
||||||
f"-Xmx{_gibs_to_mibs(max_mem)}M "
|
# Let's check for and setup for install server commands
|
||||||
f"-jar {_wrap_jar_if_windows()} nogui"
|
if create_data["type"] == "forge":
|
||||||
)
|
server_command = (
|
||||||
|
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||||
|
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||||
|
f'-jar "{server_file}" --installServer'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
server_command = (
|
||||||
|
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||||
|
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||||
|
f'-jar "{server_file}" nogui'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if create_data["type"] == "forge":
|
||||||
|
server_command = (
|
||||||
|
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||||
|
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||||
|
f"-jar {server_file} --installServer"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
server_command = (
|
||||||
|
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||||
|
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||||
|
f"-jar {server_file} nogui"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
server_command = (
|
||||||
|
f"java -Xms{_gibs_to_mibs(min_mem)}M "
|
||||||
|
f"-Xmx{_gibs_to_mibs(max_mem)}M "
|
||||||
|
f"-jar {_wrap_jar_if_windows()} nogui"
|
||||||
|
)
|
||||||
|
|
||||||
elif data["create_type"] == "minecraft_bedrock":
|
elif data["create_type"] == "minecraft_bedrock":
|
||||||
if root_create_data["create_type"] == "import_server":
|
if root_create_data["create_type"] == "import_server":
|
||||||
existing_server_path = Helpers.get_os_understandable_path(
|
existing_server_path = Helpers.get_os_understandable_path(
|
||||||
create_data["existing_server_path"]
|
create_data["existing_server_path"]
|
||||||
)
|
)
|
||||||
try:
|
if Helpers.is_os_windows():
|
||||||
FileHelpers.copy_dir(existing_server_path, new_server_path, True)
|
server_command = (
|
||||||
except shutil.Error as ex:
|
f'"{os.path.join(new_server_path, create_data["executable"])}"'
|
||||||
logger.error(f"Server import failed with error: {ex}")
|
)
|
||||||
|
else:
|
||||||
|
server_command = f"./{create_data['executable']}"
|
||||||
|
logger.debug("command: " + server_command)
|
||||||
|
server_file = create_data["executable"]
|
||||||
elif root_create_data["create_type"] == "import_zip":
|
elif root_create_data["create_type"] == "import_zip":
|
||||||
# TODO: Copy files from the zip file to the new server directory
|
# TODO: Copy files from the zip file to the new server directory
|
||||||
raise NotImplementedError("Not yet implemented")
|
raise NotImplementedError("Not yet implemented")
|
||||||
|
else:
|
||||||
|
server_file = "bedrock_server"
|
||||||
|
if Helpers.is_os_windows():
|
||||||
|
# if this is windows we will override the linux bedrock server name.
|
||||||
|
server_file = "bedrock_server.exe"
|
||||||
|
|
||||||
|
full_jar_path = os.path.join(new_server_path, server_file)
|
||||||
|
|
||||||
|
if self.helper.is_os_windows():
|
||||||
|
server_command = f'"{full_jar_path}"'
|
||||||
|
else:
|
||||||
|
server_command = f"./{server_file}"
|
||||||
_create_server_properties_if_needed(0, True)
|
_create_server_properties_if_needed(0, True)
|
||||||
|
|
||||||
server_command = create_data["command"]
|
server_command = create_data.get("command", server_command)
|
||||||
server_file = (
|
|
||||||
"./bedrock_server" # HACK: This is a hack to make the server start
|
|
||||||
)
|
|
||||||
elif data["create_type"] == "custom":
|
elif data["create_type"] == "custom":
|
||||||
# TODO: working_directory, executable_update
|
# TODO: working_directory, executable_update
|
||||||
if root_create_data["create_type"] == "raw_exec":
|
if root_create_data["create_type"] == "raw_exec":
|
||||||
@ -451,131 +499,85 @@ class Controller:
|
|||||||
server_host=monitoring_host,
|
server_host=monitoring_host,
|
||||||
server_type=monitoring_type,
|
server_type=monitoring_type,
|
||||||
)
|
)
|
||||||
|
if data["create_type"] == "minecraft_java":
|
||||||
if (
|
if root_create_data["create_type"] == "download_jar":
|
||||||
data["create_type"] == "minecraft_java"
|
# modded update urls from server jars will only update the installer
|
||||||
and root_create_data["create_type"] == "download_jar"
|
if create_data["category"] != "modded":
|
||||||
):
|
server_obj = self.servers.get_server_obj(new_server_id)
|
||||||
# modded update urls from server jars will only update the installer
|
url = (
|
||||||
if create_data["category"] != "modded":
|
f"https://serverjars.com/api/fetchJar/{create_data['category']}"
|
||||||
server_obj = self.servers.get_server_obj(new_server_id)
|
f"/{create_data['type']}/{create_data['version']}"
|
||||||
url = (
|
)
|
||||||
f"https://serverjars.com/api/fetchJar/{create_data['category']}"
|
server_obj.executable_update_url = url
|
||||||
f"/{create_data['type']}/{create_data['version']}"
|
self.servers.update_server(server_obj)
|
||||||
|
self.server_jars.download_jar(
|
||||||
|
create_data["category"],
|
||||||
|
create_data["type"],
|
||||||
|
create_data["version"],
|
||||||
|
full_jar_path,
|
||||||
|
new_server_id,
|
||||||
)
|
)
|
||||||
server_obj.executable_update_url = url
|
elif root_create_data["create_type"] == "import_server":
|
||||||
self.servers.update_server(server_obj)
|
ServersController.set_import(new_server_id)
|
||||||
self.server_jars.download_jar(
|
self.import_helper.import_jar_server(
|
||||||
create_data["category"],
|
create_data["existing_server_path"],
|
||||||
create_data["type"],
|
new_server_path,
|
||||||
create_data["version"],
|
monitoring_port,
|
||||||
full_jar_path,
|
new_server_id,
|
||||||
new_server_id,
|
)
|
||||||
)
|
elif root_create_data["create_type"] == "import_zip":
|
||||||
|
ServersController.set_import(new_server_id)
|
||||||
|
|
||||||
|
elif data["create_type"] == "minecraft_bedrock":
|
||||||
|
if root_create_data["create_type"] == "download_exe":
|
||||||
|
ServersController.set_import(new_server_id)
|
||||||
|
self.import_helper.download_bedrock_server(
|
||||||
|
new_server_path, new_server_id
|
||||||
|
)
|
||||||
|
elif root_create_data["create_type"] == "import_server":
|
||||||
|
ServersController.set_import(new_server_id)
|
||||||
|
full_exe_path = os.path.join(new_server_path, create_data["executable"])
|
||||||
|
self.import_helper.import_bedrock_server(
|
||||||
|
create_data["existing_server_path"],
|
||||||
|
new_server_path,
|
||||||
|
monitoring_port,
|
||||||
|
full_exe_path,
|
||||||
|
new_server_id,
|
||||||
|
)
|
||||||
|
elif root_create_data["create_type"] == "import_zip":
|
||||||
|
ServersController.set_import(new_server_id)
|
||||||
|
full_exe_path = os.path.join(new_server_path, create_data["executable"])
|
||||||
|
self.import_helper.import_bedrock_zip_server(
|
||||||
|
create_data["zip_path"],
|
||||||
|
new_server_path,
|
||||||
|
os.path.join(create_data["zip_root"], create_data["executable"]),
|
||||||
|
monitoring_port,
|
||||||
|
new_server_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
exec_user = self.users.get_user_by_id(int(user_id))
|
||||||
|
captured_roles = data.get("roles", [])
|
||||||
|
# These lines create a new Role for the Server with full permissions
|
||||||
|
# and add the user to it if he's not a superuser
|
||||||
|
if len(captured_roles) == 0:
|
||||||
|
if not exec_user["superuser"]:
|
||||||
|
new_server_uuid = self.servers.get_server_data_by_id(new_server_id).get(
|
||||||
|
"server_uuid"
|
||||||
|
)
|
||||||
|
role_id = self.roles.add_role(
|
||||||
|
f"Creator of Server with uuid={new_server_uuid}",
|
||||||
|
exec_user["user_id"],
|
||||||
|
)
|
||||||
|
self.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||||
|
self.users.add_role_to_user(exec_user["user_id"], role_id)
|
||||||
|
|
||||||
|
else:
|
||||||
|
for role in captured_roles:
|
||||||
|
role_id = role
|
||||||
|
self.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||||
|
|
||||||
return new_server_id, server_fs_uuid
|
return new_server_id, server_fs_uuid
|
||||||
|
|
||||||
def create_jar_server(
|
|
||||||
self,
|
|
||||||
jar: str,
|
|
||||||
server: str,
|
|
||||||
version: str,
|
|
||||||
name: str,
|
|
||||||
min_mem: int,
|
|
||||||
max_mem: int,
|
|
||||||
port: int,
|
|
||||||
user_id: int,
|
|
||||||
):
|
|
||||||
server_id = Helpers.create_uuid()
|
|
||||||
server_dir = os.path.join(self.helper.servers_dir, server_id)
|
|
||||||
backup_path = os.path.join(self.helper.backup_path, server_id)
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
server_dir = Helpers.wtol_path(server_dir)
|
|
||||||
backup_path = Helpers.wtol_path(backup_path)
|
|
||||||
server_dir.replace(" ", "^ ")
|
|
||||||
backup_path.replace(" ", "^ ")
|
|
||||||
|
|
||||||
server_file = f"{server}-{version}.jar"
|
|
||||||
|
|
||||||
# make the dir - perhaps a UUID?
|
|
||||||
Helpers.ensure_dir_exists(server_dir)
|
|
||||||
Helpers.ensure_dir_exists(backup_path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# do a eula.txt
|
|
||||||
with open(
|
|
||||||
os.path.join(server_dir, "eula.txt"), "w", encoding="utf-8"
|
|
||||||
) as file:
|
|
||||||
file.write("eula=false")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
# setup server.properties with the port
|
|
||||||
with open(
|
|
||||||
os.path.join(server_dir, "server.properties"), "w", encoding="utf-8"
|
|
||||||
) as file:
|
|
||||||
file.write(f"server-port={port}")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Unable to create required server files due to :{e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
# Let's check for and setup for install server commands
|
|
||||||
if server == "forge":
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f'-jar "{server_file}" --installServer'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f'-jar "{server_file}" nogui'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if server == "forge":
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f"-jar {server_file} --installServer"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f"-jar {server_file} nogui"
|
|
||||||
)
|
|
||||||
server_log_file = "./logs/latest.log"
|
|
||||||
server_stop = "stop"
|
|
||||||
|
|
||||||
new_id = self.register_server(
|
|
||||||
name,
|
|
||||||
server_id,
|
|
||||||
server_dir,
|
|
||||||
backup_path,
|
|
||||||
server_command,
|
|
||||||
server_file,
|
|
||||||
server_log_file,
|
|
||||||
server_stop,
|
|
||||||
port,
|
|
||||||
user_id,
|
|
||||||
server_type="minecraft-java",
|
|
||||||
)
|
|
||||||
# modded update urls from server jars will only update the installer
|
|
||||||
if jar != "modded":
|
|
||||||
server_obj = self.servers.get_server_obj(new_id)
|
|
||||||
url = f"https://serverjars.com/api/fetchJar/{jar}/{server}/{version}"
|
|
||||||
server_obj.executable_update_url = url
|
|
||||||
self.servers.update_server(server_obj)
|
|
||||||
# download the jar
|
|
||||||
self.server_jars.download_jar(
|
|
||||||
jar, server, version, os.path.join(server_dir, server_file), new_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return new_id
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def verify_jar_server(server_path: str, server_jar: str):
|
def verify_jar_server(server_path: str, server_jar: str):
|
||||||
server_path = Helpers.get_os_understandable_path(server_path)
|
server_path = Helpers.get_os_understandable_path(server_path)
|
||||||
@ -593,123 +595,6 @@ class Controller:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def import_jar_server(
|
|
||||||
self,
|
|
||||||
server_name: str,
|
|
||||||
server_path: str,
|
|
||||||
server_jar: str,
|
|
||||||
min_mem: int,
|
|
||||||
max_mem: int,
|
|
||||||
port: int,
|
|
||||||
user_id: int,
|
|
||||||
):
|
|
||||||
server_id = Helpers.create_uuid()
|
|
||||||
new_server_dir = os.path.join(self.helper.servers_dir, server_id)
|
|
||||||
backup_path = os.path.join(self.helper.backup_path, server_id)
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
new_server_dir = Helpers.wtol_path(new_server_dir)
|
|
||||||
backup_path = Helpers.wtol_path(backup_path)
|
|
||||||
new_server_dir.replace(" ", "^ ")
|
|
||||||
backup_path.replace(" ", "^ ")
|
|
||||||
|
|
||||||
Helpers.ensure_dir_exists(new_server_dir)
|
|
||||||
Helpers.ensure_dir_exists(backup_path)
|
|
||||||
server_path = Helpers.get_os_understandable_path(server_path)
|
|
||||||
|
|
||||||
full_jar_path = os.path.join(new_server_dir, server_jar)
|
|
||||||
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f'-jar "{full_jar_path}" nogui'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f"-jar {full_jar_path} nogui"
|
|
||||||
)
|
|
||||||
server_log_file = "./logs/latest.log"
|
|
||||||
server_stop = "stop"
|
|
||||||
|
|
||||||
new_id = self.register_server(
|
|
||||||
server_name,
|
|
||||||
server_id,
|
|
||||||
new_server_dir,
|
|
||||||
backup_path,
|
|
||||||
server_command,
|
|
||||||
server_jar,
|
|
||||||
server_log_file,
|
|
||||||
server_stop,
|
|
||||||
port,
|
|
||||||
user_id,
|
|
||||||
server_type="minecraft-java",
|
|
||||||
)
|
|
||||||
ServersController.set_import(new_id)
|
|
||||||
self.import_helper.import_jar_server(server_path, new_server_dir, port, new_id)
|
|
||||||
return new_id
|
|
||||||
|
|
||||||
def import_zip_server(
|
|
||||||
self,
|
|
||||||
server_name: str,
|
|
||||||
zip_path: str,
|
|
||||||
server_jar: str,
|
|
||||||
min_mem: int,
|
|
||||||
max_mem: int,
|
|
||||||
port: int,
|
|
||||||
user_id: int,
|
|
||||||
):
|
|
||||||
server_id = Helpers.create_uuid()
|
|
||||||
new_server_dir = os.path.join(self.helper.servers_dir, server_id)
|
|
||||||
backup_path = os.path.join(self.helper.backup_path, server_id)
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
new_server_dir = Helpers.wtol_path(new_server_dir)
|
|
||||||
backup_path = Helpers.wtol_path(backup_path)
|
|
||||||
new_server_dir.replace(" ", "^ ")
|
|
||||||
backup_path.replace(" ", "^ ")
|
|
||||||
|
|
||||||
temp_dir = Helpers.get_os_understandable_path(zip_path)
|
|
||||||
Helpers.ensure_dir_exists(new_server_dir)
|
|
||||||
Helpers.ensure_dir_exists(backup_path)
|
|
||||||
|
|
||||||
full_jar_path = os.path.join(new_server_dir, server_jar)
|
|
||||||
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f'-jar "{full_jar_path}" nogui'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
server_command = (
|
|
||||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
|
||||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
|
||||||
f"-jar {full_jar_path} nogui"
|
|
||||||
)
|
|
||||||
logger.debug("command: " + server_command)
|
|
||||||
server_log_file = "./logs/latest.log"
|
|
||||||
server_stop = "stop"
|
|
||||||
|
|
||||||
new_id = self.register_server(
|
|
||||||
server_name,
|
|
||||||
server_id,
|
|
||||||
new_server_dir,
|
|
||||||
backup_path,
|
|
||||||
server_command,
|
|
||||||
server_jar,
|
|
||||||
server_log_file,
|
|
||||||
server_stop,
|
|
||||||
port,
|
|
||||||
user_id,
|
|
||||||
server_type="minecraft-java",
|
|
||||||
)
|
|
||||||
ServersController.set_import(new_id)
|
|
||||||
self.import_helper.import_java_zip_server(
|
|
||||||
temp_dir, new_server_dir, port, new_id
|
|
||||||
)
|
|
||||||
return new_id
|
|
||||||
|
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
# BEDROCK IMPORTS
|
# BEDROCK IMPORTS
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
@ -1066,6 +951,8 @@ class Controller:
|
|||||||
"the new directory."
|
"the new directory."
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
self.helper.dir_migration = False
|
||||||
|
|
||||||
return
|
return
|
||||||
# set the cached serve dir
|
# set the cached serve dir
|
||||||
self.helper.servers_dir = new_server_path
|
self.helper.servers_dir = new_server_path
|
||||||
|
@ -41,10 +41,10 @@ scheduler_intervals = {
|
|||||||
class TasksManager:
|
class TasksManager:
|
||||||
controller: Controller
|
controller: Controller
|
||||||
|
|
||||||
def __init__(self, helper, controller):
|
def __init__(self, helper, controller, file_helper):
|
||||||
self.helper: Helpers = helper
|
self.helper: Helpers = helper
|
||||||
self.controller: Controller = controller
|
self.controller: Controller = controller
|
||||||
self.tornado: Webserver = Webserver(helper, controller, self)
|
self.tornado: Webserver = Webserver(helper, controller, self, file_helper)
|
||||||
try:
|
try:
|
||||||
self.tz = get_localzone()
|
self.tz = get_localzone()
|
||||||
except ZoneInfoNotFoundError as e:
|
except ZoneInfoNotFoundError as e:
|
||||||
@ -726,12 +726,21 @@ class TasksManager:
|
|||||||
def check_for_updates(self):
|
def check_for_updates(self):
|
||||||
logger.info("Checking for Crafty updates...")
|
logger.info("Checking for Crafty updates...")
|
||||||
self.helper.update_available = self.helper.check_remote_version()
|
self.helper.update_available = self.helper.check_remote_version()
|
||||||
|
remote = self.helper.update_available
|
||||||
if self.helper.update_available:
|
if self.helper.update_available:
|
||||||
logger.info(f"Found new version {self.helper.update_available}")
|
logger.info(f"Found new version {self.helper.update_available}")
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
"No updates found! You are on the most up to date Crafty version."
|
"No updates found! You are on the most up to date Crafty version."
|
||||||
)
|
)
|
||||||
|
if self.helper.update_available:
|
||||||
|
self.helper.update_available = {
|
||||||
|
"id": str(remote),
|
||||||
|
"title": f"{remote} Update Available",
|
||||||
|
"date": "",
|
||||||
|
"desc": "Release notes are available by clicking this notification.",
|
||||||
|
"link": "https://gitlab.com/crafty-controller/crafty-4/-/releases",
|
||||||
|
}
|
||||||
logger.info("Refreshing Gravatar PFPs...")
|
logger.info("Refreshing Gravatar PFPs...")
|
||||||
for user in HelperUsers.get_all_users():
|
for user in HelperUsers.get_all_users():
|
||||||
if user.email:
|
if user.email:
|
||||||
|
@ -1,698 +0,0 @@
|
|||||||
import os
|
|
||||||
import html
|
|
||||||
import pathlib
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import urllib.parse
|
|
||||||
import bleach
|
|
||||||
import tornado.web
|
|
||||||
import tornado.escape
|
|
||||||
|
|
||||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
|
||||||
from app.classes.shared.console import Console
|
|
||||||
from app.classes.shared.helpers import Helpers
|
|
||||||
from app.classes.shared.server import ServerOutBuf
|
|
||||||
from app.classes.web.base_handler import BaseHandler
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AjaxHandler(BaseHandler):
|
|
||||||
def render_page(self, template, page_data):
|
|
||||||
self.render(
|
|
||||||
template,
|
|
||||||
data=page_data,
|
|
||||||
translate=self.translator.translate,
|
|
||||||
)
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def get(self, page):
|
|
||||||
_, _, exec_user = self.current_user
|
|
||||||
error = bleach.clean(self.get_argument("error", "WTF Error!"))
|
|
||||||
|
|
||||||
template = "panel/denied.html"
|
|
||||||
|
|
||||||
page_data = {"user_data": exec_user, "error": error}
|
|
||||||
|
|
||||||
if page == "error":
|
|
||||||
template = "public/error.html"
|
|
||||||
self.render_page(template, page_data)
|
|
||||||
|
|
||||||
elif page == "server_log":
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
full_log = self.get_argument("full", False)
|
|
||||||
|
|
||||||
if server_id is None:
|
|
||||||
logger.warning("Server ID not found in server_log ajax call")
|
|
||||||
self.redirect("/panel/error?error=Server ID Not Found")
|
|
||||||
return
|
|
||||||
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
|
||||||
if not server_data:
|
|
||||||
logger.warning("Server Data not found in server_log ajax call")
|
|
||||||
self.redirect("/panel/error?error=Server ID Not Found")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not server_data["log_path"]:
|
|
||||||
logger.warning(
|
|
||||||
f"Log path not found in server_log ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
|
|
||||||
if full_log:
|
|
||||||
log_lines = self.helper.get_setting("max_log_lines")
|
|
||||||
data = Helpers.tail_file(
|
|
||||||
# If the log path is absolute it returns it as is
|
|
||||||
# If it is relative it joins the paths below like normal
|
|
||||||
pathlib.Path(server_data["path"], server_data["log_path"]),
|
|
||||||
log_lines,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data = ServerOutBuf.lines.get(server_id, [])
|
|
||||||
|
|
||||||
for line in data:
|
|
||||||
try:
|
|
||||||
line = re.sub("(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)", "", line)
|
|
||||||
line = re.sub("[A-z]{2}\b\b", "", line)
|
|
||||||
line = self.helper.log_colors(html.escape(line))
|
|
||||||
self.write(f"<span class='box'>{line}<br /></span>")
|
|
||||||
# self.write(d.encode("utf-8"))
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Skipping Log Line due to error: {e}")
|
|
||||||
|
|
||||||
elif page == "announcements":
|
|
||||||
data = Helpers.get_announcements()
|
|
||||||
page_data["notify_data"] = data
|
|
||||||
self.render_page("ajax/notify.html", page_data)
|
|
||||||
|
|
||||||
elif page == "get_zip_tree":
|
|
||||||
path = self.get_argument("path", None)
|
|
||||||
|
|
||||||
self.write(
|
|
||||||
Helpers.get_os_understandable_path(path)
|
|
||||||
+ "\n"
|
|
||||||
+ Helpers.generate_zip_tree(path)
|
|
||||||
)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
elif page == "get_zip_dir":
|
|
||||||
path = self.get_argument("path", None)
|
|
||||||
|
|
||||||
self.write(
|
|
||||||
Helpers.get_os_understandable_path(path)
|
|
||||||
+ "\n"
|
|
||||||
+ Helpers.generate_zip_dir(path)
|
|
||||||
)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
elif page == "get_backup_tree":
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
folder = self.get_argument("path", None)
|
|
||||||
|
|
||||||
output = ""
|
|
||||||
|
|
||||||
dir_list = []
|
|
||||||
unsorted_files = []
|
|
||||||
file_list = os.listdir(folder)
|
|
||||||
for item in file_list:
|
|
||||||
if os.path.isdir(os.path.join(folder, item)):
|
|
||||||
dir_list.append(item)
|
|
||||||
else:
|
|
||||||
unsorted_files.append(item)
|
|
||||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
|
||||||
unsorted_files, key=str.casefold
|
|
||||||
)
|
|
||||||
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
|
|
||||||
for raw_filename in file_list:
|
|
||||||
filename = html.escape(raw_filename)
|
|
||||||
rel = os.path.join(folder, raw_filename)
|
|
||||||
dpath = os.path.join(folder, filename)
|
|
||||||
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
|
|
||||||
server_id
|
|
||||||
):
|
|
||||||
if os.path.isdir(rel):
|
|
||||||
output += f"""<li class="tree-item" data-path="{dpath}">
|
|
||||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
|
||||||
<input type="checkbox" class="checkBoxClass" name="root_path" value="{dpath}" checked>
|
|
||||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
|
||||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
|
||||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
|
||||||
<strong>{filename}</strong>
|
|
||||||
</span>
|
|
||||||
</input></div><li>
|
|
||||||
\n"""
|
|
||||||
else:
|
|
||||||
output += f"""<li
|
|
||||||
class="d-block tree-ctx-item tree-file"
|
|
||||||
data-path="{dpath}"
|
|
||||||
data-name="{filename}"
|
|
||||||
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}" checked><span style="margin-right: 6px;">
|
|
||||||
<i class="far fa-file"></i></span></input>{filename}</li>"""
|
|
||||||
|
|
||||||
else:
|
|
||||||
if os.path.isdir(rel):
|
|
||||||
output += f"""<li class="tree-item" data-path="{dpath}">
|
|
||||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
|
||||||
<input type="checkbox" class="checkBoxClass" name="root_path" value="{dpath}">
|
|
||||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
|
||||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
|
||||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
|
||||||
<strong>{filename}</strong>
|
|
||||||
</span>
|
|
||||||
</input></div><li>
|
|
||||||
\n"""
|
|
||||||
else:
|
|
||||||
output += f"""<li
|
|
||||||
class="d-block tree-ctx-item tree-file"
|
|
||||||
data-path="{dpath}"
|
|
||||||
data-name="{filename}"
|
|
||||||
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}">
|
|
||||||
<span style="margin-right: 6px;"><i class="far fa-file">
|
|
||||||
</i></span></input>{filename}</li>"""
|
|
||||||
self.write(Helpers.get_os_understandable_path(folder) + "\n" + output)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
elif page == "get_backup_dir":
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
folder = self.get_argument("path", None)
|
|
||||||
output = ""
|
|
||||||
|
|
||||||
dir_list = []
|
|
||||||
unsorted_files = []
|
|
||||||
file_list = os.listdir(folder)
|
|
||||||
for item in file_list:
|
|
||||||
if os.path.isdir(os.path.join(folder, item)):
|
|
||||||
dir_list.append(item)
|
|
||||||
else:
|
|
||||||
unsorted_files.append(item)
|
|
||||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
|
||||||
unsorted_files, key=str.casefold
|
|
||||||
)
|
|
||||||
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
|
|
||||||
for raw_filename in file_list:
|
|
||||||
filename = html.escape(raw_filename)
|
|
||||||
rel = os.path.join(folder, raw_filename)
|
|
||||||
dpath = os.path.join(folder, filename)
|
|
||||||
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
|
|
||||||
server_id
|
|
||||||
):
|
|
||||||
if os.path.isdir(rel):
|
|
||||||
output += f"""<li class="tree-item" data-path="{dpath}">
|
|
||||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
|
||||||
<input type="checkbox" name="root_path" value="{dpath}" checked>
|
|
||||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
|
||||||
<i class="far fa-folder"></i>
|
|
||||||
<i class="far fa-folder-open"></i>
|
|
||||||
<strong>{filename}</strong>
|
|
||||||
</span>
|
|
||||||
</input></div><li>"""
|
|
||||||
else:
|
|
||||||
output += f"""<li
|
|
||||||
class="tree-item tree-nested d-block tree-ctx-item tree-file"
|
|
||||||
data-path="{dpath}"
|
|
||||||
data-name="{filename}"
|
|
||||||
onclick=""><input type='checkbox' name='root_path' value='{dpath}' checked><span style="margin-right: 6px;">
|
|
||||||
<i class="far fa-file"></i></span></input>{filename}</li>"""
|
|
||||||
|
|
||||||
else:
|
|
||||||
if os.path.isdir(rel):
|
|
||||||
output += f"""<li class="tree-item" data-path="{dpath}">
|
|
||||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
|
||||||
<input type="checkbox" name="root_path" value="{dpath}">
|
|
||||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
|
||||||
<i class="far fa-folder"></i>
|
|
||||||
<i class="far fa-folder-open"></i>
|
|
||||||
<strong>{filename}</strong>
|
|
||||||
</span>
|
|
||||||
</input></div><li>"""
|
|
||||||
else:
|
|
||||||
output += f"""<li
|
|
||||||
class="tree-item tree-nested d-block tree-ctx-item tree-file"
|
|
||||||
data-path="{dpath}"
|
|
||||||
data-name="{filename}"
|
|
||||||
onclick=""><input type='checkbox' name='root_path' value='{dpath}'>
|
|
||||||
<span style="margin-right: 6px;"><i class="far fa-file">
|
|
||||||
</i></span></input>{filename}</li>"""
|
|
||||||
|
|
||||||
self.write(Helpers.get_os_understandable_path(folder) + "\n" + output)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
elif page == "get_dir":
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
path = self.get_argument("path", None)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "get_tree"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if Helpers.validate_traversal(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"], path
|
|
||||||
):
|
|
||||||
self.write(
|
|
||||||
Helpers.get_os_understandable_path(path)
|
|
||||||
+ "\n"
|
|
||||||
+ Helpers.generate_dir(path)
|
|
||||||
)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def post(self, page):
|
|
||||||
api_key, _, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
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")
|
|
||||||
|
|
||||||
svr_obj = self.controller.servers.get_server_instance_by_id(server_id)
|
|
||||||
|
|
||||||
if command == svr_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 svr_obj.check_running():
|
|
||||||
svr_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 exec_user["superuser"]:
|
|
||||||
photo = urllib.parse.unquote(self.get_argument("photo", ""))
|
|
||||||
opacity = self.get_argument("opacity", 100)
|
|
||||||
self.controller.management.set_login_opacity(int(opacity))
|
|
||||||
if photo == "login_1.jpg":
|
|
||||||
self.controller.management.set_login_image("login_1.jpg")
|
|
||||||
self.controller.cached_login = f"{photo}"
|
|
||||||
else:
|
|
||||||
self.controller.management.set_login_image(f"custom/{photo}")
|
|
||||||
self.controller.cached_login = f"custom/{photo}"
|
|
||||||
return
|
|
||||||
|
|
||||||
elif page == "delete_photo":
|
|
||||||
if exec_user["superuser"]:
|
|
||||||
photo = urllib.parse.unquote(self.get_argument("photo", None))
|
|
||||||
if photo and photo != "login_1.jpg":
|
|
||||||
os.remove(
|
|
||||||
os.path.join(
|
|
||||||
self.controller.project_root,
|
|
||||||
f"app/frontend/static/assets/images/auth/custom/{photo}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
current = self.controller.cached_login
|
|
||||||
split = current.split("/")
|
|
||||||
if len(split) == 1:
|
|
||||||
current_photo = current
|
|
||||||
else:
|
|
||||||
current_photo = split[1]
|
|
||||||
if current_photo == photo:
|
|
||||||
self.controller.management.set_login_image("login_1.jpg")
|
|
||||||
self.controller.cached_login = "login_1.jpg"
|
|
||||||
return
|
|
||||||
|
|
||||||
elif page == "eula":
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
svr = self.controller.servers.get_server_instance_by_id(server_id)
|
|
||||||
svr.agree_eula(exec_user["user_id"])
|
|
||||||
|
|
||||||
elif page == "restore_backup":
|
|
||||||
if not permissions["Backup"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Backups")
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(self.get_argument("id", None))
|
|
||||||
zip_name = bleach.clean(self.get_argument("zip_file", None))
|
|
||||||
svr_obj = self.controller.servers.get_server_obj(server_id)
|
|
||||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
|
||||||
|
|
||||||
# import the server again based on zipfile
|
|
||||||
if server_data["type"] == "minecraft-java":
|
|
||||||
backup_path = svr_obj.backup_path
|
|
||||||
if Helpers.validate_traversal(backup_path, zip_name):
|
|
||||||
temp_dir = Helpers.unzip_backup_archive(backup_path, zip_name)
|
|
||||||
new_server = self.controller.import_zip_server(
|
|
||||||
svr_obj.server_name,
|
|
||||||
temp_dir,
|
|
||||||
server_data["executable"],
|
|
||||||
"1",
|
|
||||||
"2",
|
|
||||||
server_data["server_port"],
|
|
||||||
server_data["created_by"],
|
|
||||||
)
|
|
||||||
new_server_id = new_server
|
|
||||||
new_server = self.controller.servers.get_server_data(new_server)
|
|
||||||
self.controller.rename_backup_dir(
|
|
||||||
server_id, new_server_id, new_server["server_uuid"]
|
|
||||||
)
|
|
||||||
# preserve current schedules
|
|
||||||
for schedule in self.controller.management.get_schedules_by_server(
|
|
||||||
server_id
|
|
||||||
):
|
|
||||||
self.tasks_manager.update_job(
|
|
||||||
schedule.schedule_id, {"server_id": new_server_id}
|
|
||||||
)
|
|
||||||
# preserve execution command
|
|
||||||
new_server_obj = self.controller.servers.get_server_obj(
|
|
||||||
new_server_id
|
|
||||||
)
|
|
||||||
new_server_obj.execution_command = server_data["execution_command"]
|
|
||||||
# reset executable path
|
|
||||||
if svr_obj.path in svr_obj.executable:
|
|
||||||
new_server_obj.executable = str(svr_obj.executable).replace(
|
|
||||||
svr_obj.path, new_server_obj.path
|
|
||||||
)
|
|
||||||
# reset run command path
|
|
||||||
if svr_obj.path in svr_obj.execution_command:
|
|
||||||
new_server_obj.execution_command = str(
|
|
||||||
svr_obj.execution_command
|
|
||||||
).replace(svr_obj.path, new_server_obj.path)
|
|
||||||
# reset log path
|
|
||||||
if svr_obj.path in svr_obj.log_path:
|
|
||||||
new_server_obj.log_path = str(svr_obj.log_path).replace(
|
|
||||||
svr_obj.path, new_server_obj.path
|
|
||||||
)
|
|
||||||
self.controller.servers.update_server(new_server_obj)
|
|
||||||
|
|
||||||
# preserve backup config
|
|
||||||
backup_config = self.controller.management.get_backup_config(
|
|
||||||
server_id
|
|
||||||
)
|
|
||||||
excluded_dirs = []
|
|
||||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
|
||||||
loop_backup_path = self.helper.wtol_path(server_obj.path)
|
|
||||||
for item in self.controller.management.get_excluded_backup_dirs(
|
|
||||||
server_id
|
|
||||||
):
|
|
||||||
item_path = self.helper.wtol_path(item)
|
|
||||||
bu_path = os.path.relpath(item_path, loop_backup_path)
|
|
||||||
bu_path = os.path.join(new_server_obj.path, bu_path)
|
|
||||||
excluded_dirs.append(bu_path)
|
|
||||||
self.controller.management.set_backup_config(
|
|
||||||
new_server_id,
|
|
||||||
new_server_obj.backup_path,
|
|
||||||
backup_config["max_backups"],
|
|
||||||
excluded_dirs,
|
|
||||||
backup_config["compress"],
|
|
||||||
backup_config["shutdown"],
|
|
||||||
)
|
|
||||||
# remove old server's tasks
|
|
||||||
try:
|
|
||||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
|
||||||
except:
|
|
||||||
logger.info("No active tasks found for server")
|
|
||||||
self.controller.remove_server(server_id, True)
|
|
||||||
self.redirect("/panel/dashboard")
|
|
||||||
|
|
||||||
else:
|
|
||||||
backup_path = svr_obj.backup_path
|
|
||||||
if Helpers.validate_traversal(backup_path, zip_name):
|
|
||||||
temp_dir = Helpers.unzip_backup_archive(backup_path, zip_name)
|
|
||||||
new_server = self.controller.import_bedrock_zip_server(
|
|
||||||
svr_obj.server_name,
|
|
||||||
temp_dir,
|
|
||||||
server_data["executable"],
|
|
||||||
server_data["server_port"],
|
|
||||||
server_data["created_by"],
|
|
||||||
)
|
|
||||||
new_server_id = new_server
|
|
||||||
new_server = self.controller.servers.get_server_data(new_server)
|
|
||||||
self.controller.rename_backup_dir(
|
|
||||||
server_id, new_server_id, new_server["server_uuid"]
|
|
||||||
)
|
|
||||||
# preserve current schedules
|
|
||||||
for schedule in self.controller.management.get_schedules_by_server(
|
|
||||||
server_id
|
|
||||||
):
|
|
||||||
self.tasks_manager.update_job(
|
|
||||||
schedule.schedule_id, {"server_id": new_server_id}
|
|
||||||
)
|
|
||||||
# preserve execution command
|
|
||||||
new_server_obj = self.controller.servers.get_server_obj(
|
|
||||||
new_server_id
|
|
||||||
)
|
|
||||||
new_server_obj.execution_command = server_data["execution_command"]
|
|
||||||
# reset executable path
|
|
||||||
if server_obj.path in server_obj.executable:
|
|
||||||
new_server_obj.executable = str(server_obj.executable).replace(
|
|
||||||
server_obj.path, new_server_obj.path
|
|
||||||
)
|
|
||||||
# reset run command path
|
|
||||||
if server_obj.path in server_obj.execution_command:
|
|
||||||
new_server_obj.execution_command = str(
|
|
||||||
server_obj.execution_command
|
|
||||||
).replace(server_obj.path, new_server_obj.path)
|
|
||||||
# reset log path
|
|
||||||
if server_obj.path in server_obj.log_path:
|
|
||||||
new_server_obj.log_path = str(server_obj.log_path).replace(
|
|
||||||
server_obj.path, new_server_obj.path
|
|
||||||
)
|
|
||||||
self.controller.servers.update_server(new_server_obj)
|
|
||||||
|
|
||||||
# preserve backup config
|
|
||||||
backup_config = self.controller.management.get_backup_config(
|
|
||||||
server_id
|
|
||||||
)
|
|
||||||
excluded_dirs = []
|
|
||||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
|
||||||
loop_backup_path = self.helper.wtol_path(server_obj.path)
|
|
||||||
for item in self.controller.management.get_excluded_backup_dirs(
|
|
||||||
server_id
|
|
||||||
):
|
|
||||||
item_path = self.helper.wtol_path(item)
|
|
||||||
bu_path = os.path.relpath(item_path, loop_backup_path)
|
|
||||||
bu_path = os.path.join(new_server_obj.path, bu_path)
|
|
||||||
excluded_dirs.append(bu_path)
|
|
||||||
self.controller.management.set_backup_config(
|
|
||||||
new_server_id,
|
|
||||||
new_server_obj.backup_path,
|
|
||||||
backup_config["max_backups"],
|
|
||||||
excluded_dirs,
|
|
||||||
backup_config["compress"],
|
|
||||||
backup_config["shutdown"],
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
|
||||||
except:
|
|
||||||
logger.info("No active tasks found for server")
|
|
||||||
self.controller.remove_server(server_id, True)
|
|
||||||
self.redirect("/panel/dashboard")
|
|
||||||
|
|
||||||
elif page == "unzip_server":
|
|
||||||
path = urllib.parse.unquote(self.get_argument("path", ""))
|
|
||||||
if not path:
|
|
||||||
path = os.path.join(
|
|
||||||
self.controller.project_root,
|
|
||||||
"imports",
|
|
||||||
urllib.parse.unquote(self.get_argument("file", "")),
|
|
||||||
)
|
|
||||||
if Helpers.check_file_exists(path):
|
|
||||||
self.helper.unzip_server(path, exec_user["user_id"])
|
|
||||||
else:
|
|
||||||
user_id = exec_user["user_id"]
|
|
||||||
if user_id:
|
|
||||||
time.sleep(5)
|
|
||||||
user_lang = self.controller.users.get_user_lang_by_id(user_id)
|
|
||||||
self.helper.websocket_helper.broadcast_user(
|
|
||||||
user_id,
|
|
||||||
"send_start_error",
|
|
||||||
{
|
|
||||||
"error": self.helper.translation.translate(
|
|
||||||
"error", "no-file", user_lang
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
elif page == "backup_select":
|
|
||||||
path = self.get_argument("path", None)
|
|
||||||
self.helper.backup_select(path, exec_user["user_id"])
|
|
||||||
return
|
|
||||||
|
|
||||||
elif page == "jar_cache":
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Not a super user")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.controller.server_jars.manual_refresh_cache()
|
|
||||||
return
|
|
||||||
|
|
||||||
elif page == "update_server_dir":
|
|
||||||
if self.helper.dir_migration:
|
|
||||||
return
|
|
||||||
for server in self.controller.servers.get_all_servers_stats():
|
|
||||||
if server["stats"]["running"]:
|
|
||||||
self.helper.websocket_helper.broadcast_user(
|
|
||||||
exec_user["user_id"],
|
|
||||||
"send_start_error",
|
|
||||||
{
|
|
||||||
"error": "You must stop all servers before "
|
|
||||||
"starting a storage migration."
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Not a super user")
|
|
||||||
return
|
|
||||||
if self.helper.is_env_docker():
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=This feature is not"
|
|
||||||
" supported on docker environments"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
new_dir = urllib.parse.unquote(self.get_argument("server_dir"))
|
|
||||||
self.controller.update_master_server_dir(new_dir, exec_user["user_id"])
|
|
||||||
return
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def delete(self, page):
|
|
||||||
api_key, _, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
exec_user["user_id"], server_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if page == "del_backup":
|
|
||||||
if not permissions["Backup"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Backups")
|
|
||||||
return
|
|
||||||
file_path = Helpers.get_os_understandable_path(
|
|
||||||
self.get_body_argument("file_path", default=None, strip=True)
|
|
||||||
)
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
|
|
||||||
Console.warning(f"Delete {file_path} for server {server_id}")
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "del_backup"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
|
||||||
if not (
|
|
||||||
self.helper.is_subdir(
|
|
||||||
file_path, Helpers.get_os_understandable_path(server_info["path"])
|
|
||||||
)
|
|
||||||
or self.helper.is_subdir(
|
|
||||||
file_path,
|
|
||||||
Helpers.get_os_understandable_path(server_info["backup_path"]),
|
|
||||||
)
|
|
||||||
) or not Helpers.check_file_exists(os.path.abspath(file_path)):
|
|
||||||
logger.warning(f"Invalid path in del_backup ajax call ({file_path})")
|
|
||||||
Console.warning(f"Invalid path in del_backup ajax call ({file_path})")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Delete the file
|
|
||||||
if Helpers.validate_traversal(
|
|
||||||
Helpers.get_os_understandable_path(server_info["backup_path"]),
|
|
||||||
file_path,
|
|
||||||
):
|
|
||||||
os.remove(file_path)
|
|
||||||
|
|
||||||
def check_server_id(self, server_id, page_name):
|
|
||||||
if server_id is None:
|
|
||||||
logger.warning(
|
|
||||||
f"Server ID not defined in {page_name} ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Server ID not defined in {page_name} ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
# does this server id exist?
|
|
||||||
if not self.controller.servers.server_id_exists(server_id):
|
|
||||||
logger.warning(
|
|
||||||
f"Server ID not found in {page_name} ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Server ID not found in {page_name} ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
return True
|
|
@ -8,6 +8,7 @@ import tornado.web
|
|||||||
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||||
from app.classes.models.users import ApiKeys
|
from app.classes.models.users import ApiKeys
|
||||||
from app.classes.shared.helpers import Helpers
|
from app.classes.shared.helpers import Helpers
|
||||||
|
from app.classes.shared.file_helpers import FileHelpers
|
||||||
from app.classes.shared.main_controller import Controller
|
from app.classes.shared.main_controller import Controller
|
||||||
from app.classes.shared.translation import Translation
|
from app.classes.shared.translation import Translation
|
||||||
from app.classes.models.management import DatabaseShortcuts
|
from app.classes.models.management import DatabaseShortcuts
|
||||||
@ -24,15 +25,22 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||||||
helper: Helpers
|
helper: Helpers
|
||||||
controller: Controller
|
controller: Controller
|
||||||
translator: Translation
|
translator: Translation
|
||||||
|
file_helper: FileHelpers
|
||||||
|
|
||||||
# noinspection PyAttributeOutsideInit
|
# noinspection PyAttributeOutsideInit
|
||||||
def initialize(
|
def initialize(
|
||||||
self, helper=None, controller=None, tasks_manager=None, translator=None
|
self,
|
||||||
|
helper=None,
|
||||||
|
controller=None,
|
||||||
|
tasks_manager=None,
|
||||||
|
translator=None,
|
||||||
|
file_helper=None,
|
||||||
):
|
):
|
||||||
self.helper = helper
|
self.helper = helper
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.tasks_manager = tasks_manager
|
self.tasks_manager = tasks_manager
|
||||||
self.translator = translator
|
self.translator = translator
|
||||||
|
self.file_helper = file_helper
|
||||||
|
|
||||||
def set_default_headers(self) -> None:
|
def set_default_headers(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1,464 +0,0 @@
|
|||||||
import os
|
|
||||||
import logging
|
|
||||||
import bleach
|
|
||||||
import tornado.web
|
|
||||||
import tornado.escape
|
|
||||||
|
|
||||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
|
||||||
from app.classes.shared.console import Console
|
|
||||||
from app.classes.shared.helpers import Helpers
|
|
||||||
from app.classes.shared.file_helpers import FileHelpers
|
|
||||||
from app.classes.web.base_handler import BaseHandler
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class FileHandler(BaseHandler):
|
|
||||||
def render_page(self, template, page_data):
|
|
||||||
self.render(
|
|
||||||
template,
|
|
||||||
data=page_data,
|
|
||||||
translate=self.translator.translate,
|
|
||||||
)
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def get(self, page):
|
|
||||||
api_key, _, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
exec_user["user_id"], server_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if page == "get_file":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
file_path = Helpers.get_os_understandable_path(
|
|
||||||
self.get_argument("file_path", None)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "get_file"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
file_path,
|
|
||||||
Helpers.get_os_understandable_path(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
|
||||||
),
|
|
||||||
) or not Helpers.check_file_exists(os.path.abspath(file_path)):
|
|
||||||
logger.warning(
|
|
||||||
f"Invalid path in get_file file file ajax call ({file_path})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Invalid path in get_file file file ajax call ({file_path})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
error = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file_path, encoding="utf-8") as file:
|
|
||||||
file_contents = file.read()
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
file_contents = ""
|
|
||||||
error = "UnicodeDecodeError"
|
|
||||||
|
|
||||||
self.write({"content": file_contents, "error": error})
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
elif page == "get_tree":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
path = self.get_argument("path", None)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "get_tree"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if Helpers.validate_traversal(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"], path
|
|
||||||
):
|
|
||||||
self.write(
|
|
||||||
Helpers.get_os_understandable_path(path)
|
|
||||||
+ "\n"
|
|
||||||
+ self.helper.generate_tree(path)
|
|
||||||
)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
elif page == "get_dir":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
path = self.get_argument("path", None)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "get_tree"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if Helpers.validate_traversal(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"], path
|
|
||||||
):
|
|
||||||
self.write(
|
|
||||||
Helpers.get_os_understandable_path(path)
|
|
||||||
+ "\n"
|
|
||||||
+ self.helper.generate_dir(path)
|
|
||||||
)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def post(self, page):
|
|
||||||
api_key, _, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
exec_user["user_id"], server_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if page == "create_file":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
file_parent = Helpers.get_os_understandable_path(
|
|
||||||
self.get_body_argument("file_parent", default=None, strip=True)
|
|
||||||
)
|
|
||||||
file_name = self.get_body_argument("file_name", default=None, strip=True)
|
|
||||||
file_path = os.path.join(file_parent, file_name)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "create_file"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
file_path,
|
|
||||||
Helpers.get_os_understandable_path(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
|
||||||
),
|
|
||||||
) or Helpers.check_file_exists(os.path.abspath(file_path)):
|
|
||||||
logger.warning(
|
|
||||||
f"Invalid path in create_file file ajax call ({file_path})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Invalid path in create_file file ajax call ({file_path})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Create the file by opening it
|
|
||||||
with open(file_path, "w", encoding="utf-8") as file_object:
|
|
||||||
file_object.close()
|
|
||||||
|
|
||||||
elif page == "create_dir":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
dir_parent = Helpers.get_os_understandable_path(
|
|
||||||
self.get_body_argument("dir_parent", default=None, strip=True)
|
|
||||||
)
|
|
||||||
dir_name = self.get_body_argument("dir_name", default=None, strip=True)
|
|
||||||
dir_path = os.path.join(dir_parent, dir_name)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "create_dir"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
dir_path,
|
|
||||||
Helpers.get_os_understandable_path(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
|
||||||
),
|
|
||||||
) or Helpers.check_path_exists(os.path.abspath(dir_path)):
|
|
||||||
logger.warning(
|
|
||||||
f"Invalid path in create_dir file ajax call ({dir_path})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Invalid path in create_dir file ajax call ({dir_path})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
# Create the directory
|
|
||||||
os.mkdir(dir_path)
|
|
||||||
|
|
||||||
elif page == "unzip_file":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
path = Helpers.get_os_understandable_path(self.get_argument("path", None))
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
path = Helpers.wtol_path(path)
|
|
||||||
FileHelpers.unzip_file(path)
|
|
||||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
|
|
||||||
return
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def delete(self, page):
|
|
||||||
api_key, _, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
exec_user["user_id"], server_id
|
|
||||||
)
|
|
||||||
if page == "del_file":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
file_path = Helpers.get_os_understandable_path(
|
|
||||||
self.get_body_argument("file_path", default=None, strip=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
Console.warning(f"Delete {file_path} for server {server_id}")
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "del_file"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
|
||||||
if not (
|
|
||||||
self.helper.is_subdir(
|
|
||||||
file_path, Helpers.get_os_understandable_path(server_info["path"])
|
|
||||||
)
|
|
||||||
or self.helper.is_subdir(
|
|
||||||
file_path,
|
|
||||||
Helpers.get_os_understandable_path(server_info["backup_path"]),
|
|
||||||
)
|
|
||||||
) or not Helpers.check_file_exists(os.path.abspath(file_path)):
|
|
||||||
logger.warning(f"Invalid path in del_file file ajax call ({file_path})")
|
|
||||||
Console.warning(
|
|
||||||
f"Invalid path in del_file file ajax call ({file_path})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Delete the file
|
|
||||||
FileHelpers.del_file(file_path)
|
|
||||||
|
|
||||||
elif page == "del_dir":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
dir_path = Helpers.get_os_understandable_path(
|
|
||||||
self.get_body_argument("dir_path", default=None, strip=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
Console.warning(f"Delete {dir_path} for server {server_id}")
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "del_dir"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
dir_path, Helpers.get_os_understandable_path(server_info["path"])
|
|
||||||
) or not Helpers.check_path_exists(os.path.abspath(dir_path)):
|
|
||||||
logger.warning(f"Invalid path in del_file file ajax call ({dir_path})")
|
|
||||||
Console.warning(f"Invalid path in del_file file ajax call ({dir_path})")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Delete the directory
|
|
||||||
# os.rmdir(dir_path) # Would only remove empty directories
|
|
||||||
if Helpers.validate_traversal(
|
|
||||||
Helpers.get_os_understandable_path(server_info["path"]), dir_path
|
|
||||||
):
|
|
||||||
# Removes also when there are contents
|
|
||||||
FileHelpers.del_dirs(dir_path)
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def put(self, page):
|
|
||||||
api_key, _, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
exec_user["user_id"], server_id
|
|
||||||
)
|
|
||||||
if page == "save_file":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
file_contents = self.get_body_argument(
|
|
||||||
"file_contents", default=None, strip=True
|
|
||||||
)
|
|
||||||
file_path = Helpers.get_os_understandable_path(
|
|
||||||
self.get_body_argument("file_path", default=None, strip=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "save_file"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
file_path,
|
|
||||||
Helpers.get_os_understandable_path(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
|
||||||
),
|
|
||||||
) or not Helpers.check_file_exists(os.path.abspath(file_path)):
|
|
||||||
logger.warning(
|
|
||||||
f"Invalid path in save_file file ajax call ({file_path})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Invalid path in save_file file ajax call ({file_path})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Open the file in write mode and store the content in file_object
|
|
||||||
with open(file_path, "w", encoding="utf-8") as file_object:
|
|
||||||
file_object.write(file_contents)
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def patch(self, page):
|
|
||||||
api_key, _, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
exec_user["user_id"], server_id
|
|
||||||
)
|
|
||||||
if page == "rename_file":
|
|
||||||
if not permissions["Files"] in user_perms:
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
|
||||||
return
|
|
||||||
item_path = Helpers.get_os_understandable_path(
|
|
||||||
self.get_body_argument("item_path", default=None, strip=True)
|
|
||||||
)
|
|
||||||
new_item_name = self.get_body_argument(
|
|
||||||
"new_item_name", default=None, strip=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.check_server_id(server_id, "rename_file"):
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
if item_path is None or new_item_name is None:
|
|
||||||
logger.warning("Invalid path(s) in rename_file file ajax call")
|
|
||||||
Console.warning("Invalid path(s) in rename_file file ajax call")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
item_path,
|
|
||||||
Helpers.get_os_understandable_path(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
|
||||||
),
|
|
||||||
) or not Helpers.check_path_exists(os.path.abspath(item_path)):
|
|
||||||
logger.warning(
|
|
||||||
f"Invalid old name path in rename_file file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Invalid old name path in rename_file file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
|
|
||||||
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
new_item_path,
|
|
||||||
Helpers.get_os_understandable_path(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
|
||||||
),
|
|
||||||
) or Helpers.check_path_exists(os.path.abspath(new_item_path)):
|
|
||||||
logger.warning(
|
|
||||||
f"Invalid new name path in rename_file file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Invalid new name path in rename_file file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# RENAME
|
|
||||||
os.rename(item_path, new_item_path)
|
|
||||||
|
|
||||||
def check_server_id(self, server_id, page_name):
|
|
||||||
if server_id is None:
|
|
||||||
logger.warning(
|
|
||||||
f"Server ID not defined in {page_name} file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Server ID not defined in {page_name} file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
server_id = bleach.clean(server_id)
|
|
||||||
|
|
||||||
# does this server id exist?
|
|
||||||
if not self.controller.servers.server_id_exists(server_id):
|
|
||||||
logger.warning(
|
|
||||||
f"Server ID not found in {page_name} file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"Server ID not found in {page_name} file ajax call ({server_id})"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
return True
|
|
@ -1534,40 +1534,8 @@ class PanelHandler(BaseHandler):
|
|||||||
|
|
||||||
template = "panel/panel_edit_role.html"
|
template = "panel/panel_edit_role.html"
|
||||||
|
|
||||||
elif page == "remove_role":
|
|
||||||
role_id = bleach.clean(self.get_argument("id", None))
|
|
||||||
|
|
||||||
if (
|
|
||||||
not superuser
|
|
||||||
and self.controller.roles.get_role(role_id)["manager"]
|
|
||||||
!= exec_user["user_id"]
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: not superuser not"
|
|
||||||
" role manager"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if role_id is None:
|
|
||||||
self.redirect("/panel/error?error=Invalid Role ID")
|
|
||||||
return
|
|
||||||
# does this user id exist?
|
|
||||||
target_role = self.controller.roles.get_role(role_id)
|
|
||||||
if not target_role:
|
|
||||||
self.redirect("/panel/error?error=Invalid Role ID")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.controller.roles.remove_role(role_id)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Removed role {target_role['role_name']} (RID:{role_id})",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.redirect("/panel/panel_config")
|
|
||||||
|
|
||||||
elif page == "activity_logs":
|
elif page == "activity_logs":
|
||||||
page_data["audit_logs"] = self.controller.management.get_actity_log()
|
page_data["audit_logs"] = self.controller.management.get_activity_log()
|
||||||
|
|
||||||
template = "panel/activity_logs.html"
|
template = "panel/activity_logs.html"
|
||||||
|
|
||||||
@ -1649,606 +1617,3 @@ class PanelHandler(BaseHandler):
|
|||||||
utc_offset=(time.timezone * -1 / 60 / 60),
|
utc_offset=(time.timezone * -1 / 60 / 60),
|
||||||
translate=self.translator.translate,
|
translate=self.translator.translate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def post(self, page):
|
|
||||||
api_key, _token_data, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
server_id = self.get_argument("id", None)
|
|
||||||
permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
}
|
|
||||||
if superuser:
|
|
||||||
# defined_servers = self.controller.servers.list_defined_servers()
|
|
||||||
exec_user_role = {"Super User"}
|
|
||||||
exec_user_crafty_permissions = (
|
|
||||||
self.controller.crafty_perms.list_defined_crafty_permissions()
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
exec_user_crafty_permissions = (
|
|
||||||
self.controller.crafty_perms.get_crafty_permissions_list(
|
|
||||||
exec_user["user_id"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# defined_servers =
|
|
||||||
# self.controller.servers.get_authorized_servers(exec_user["user_id"])
|
|
||||||
exec_user_role = set()
|
|
||||||
for r in exec_user["roles"]:
|
|
||||||
role = self.controller.roles.get_role(r)
|
|
||||||
exec_user_role.add(role["role_name"])
|
|
||||||
|
|
||||||
if page == "server_backup":
|
|
||||||
logger.debug(self.request.arguments)
|
|
||||||
|
|
||||||
server_id = self.check_server_id()
|
|
||||||
if not server_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
if (
|
|
||||||
not permissions["Backup"]
|
|
||||||
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
|
|
||||||
|
|
||||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
|
||||||
compress = self.get_argument("compress", False)
|
|
||||||
shutdown = self.get_argument("shutdown", False)
|
|
||||||
check_changed = self.get_argument("changed")
|
|
||||||
before = self.get_argument("backup_before", "")
|
|
||||||
after = self.get_argument("backup_after", "")
|
|
||||||
if str(check_changed) == str(1):
|
|
||||||
checked = self.get_body_arguments("root_path")
|
|
||||||
else:
|
|
||||||
checked = self.controller.management.get_excluded_backup_dirs(server_id)
|
|
||||||
if superuser:
|
|
||||||
backup_path = self.get_argument("backup_path", None)
|
|
||||||
if Helpers.is_os_windows():
|
|
||||||
backup_path.replace(" ", "^ ")
|
|
||||||
backup_path = Helpers.wtol_path(backup_path)
|
|
||||||
else:
|
|
||||||
backup_path = server_obj.backup_path
|
|
||||||
max_backups = bleach.clean(self.get_argument("max_backups", None))
|
|
||||||
|
|
||||||
server_obj = self.controller.servers.get_server_obj(server_id)
|
|
||||||
|
|
||||||
server_obj.backup_path = backup_path
|
|
||||||
self.controller.servers.update_server(server_obj)
|
|
||||||
self.controller.management.set_backup_config(
|
|
||||||
server_id,
|
|
||||||
max_backups=max_backups,
|
|
||||||
excluded_dirs=checked,
|
|
||||||
compress=bool(compress),
|
|
||||||
shutdown=bool(shutdown),
|
|
||||||
before=before,
|
|
||||||
after=after,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Edited server {server_id}: updated backups",
|
|
||||||
server_id,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.tasks_manager.reload_schedule_from_db()
|
|
||||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=backup")
|
|
||||||
|
|
||||||
elif page == "config_json":
|
|
||||||
try:
|
|
||||||
data = {}
|
|
||||||
with open(self.helper.settings_file, "r", encoding="utf-8") as f:
|
|
||||||
keys = json.load(f).keys()
|
|
||||||
this_uuid = self.get_argument("uuid")
|
|
||||||
for key in keys:
|
|
||||||
arg_data = self.get_argument(key)
|
|
||||||
if arg_data.startswith(this_uuid):
|
|
||||||
arg_data = arg_data.split(",")
|
|
||||||
arg_data.pop(0)
|
|
||||||
data[key] = arg_data
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
data[key] = int(arg_data)
|
|
||||||
except:
|
|
||||||
if arg_data == "True":
|
|
||||||
data[key] = True
|
|
||||||
elif arg_data == "False":
|
|
||||||
data[key] = False
|
|
||||||
else:
|
|
||||||
data[key] = arg_data
|
|
||||||
keys = list(data.keys())
|
|
||||||
keys.sort()
|
|
||||||
sorted_data = {i: data[i] for i in keys}
|
|
||||||
with open(self.helper.settings_file, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(sorted_data, f, indent=4)
|
|
||||||
except Exception as e:
|
|
||||||
logger.critical(
|
|
||||||
"Config File Error: Unable to read "
|
|
||||||
f"{self.helper.settings_file} due to {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.redirect("/panel/config_json")
|
|
||||||
|
|
||||||
elif page == "edit_user":
|
|
||||||
if bleach.clean(self.get_argument("username", None)).lower() == "system":
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: "
|
|
||||||
"system user is not editable"
|
|
||||||
)
|
|
||||||
user_id = bleach.clean(self.get_argument("id", None))
|
|
||||||
user = self.controller.users.get_user_by_id(user_id)
|
|
||||||
username = bleach.clean(self.get_argument("username", None).lower())
|
|
||||||
theme = bleach.clean(self.get_argument("theme", "default"))
|
|
||||||
if (
|
|
||||||
username != self.controller.users.get_user_by_id(user_id)["username"]
|
|
||||||
and username in self.controller.users.get_all_usernames()
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Duplicate User: Useranme already exists."
|
|
||||||
)
|
|
||||||
password0 = bleach.clean(self.get_argument("password0", None))
|
|
||||||
password1 = bleach.clean(self.get_argument("password1", None))
|
|
||||||
email = bleach.clean(self.get_argument("email", "default@example.com"))
|
|
||||||
enabled = int(float(self.get_argument("enabled", "0")))
|
|
||||||
try:
|
|
||||||
hints = int(bleach.clean(self.get_argument("hints")))
|
|
||||||
hints = True
|
|
||||||
except:
|
|
||||||
hints = False
|
|
||||||
lang = bleach.clean(
|
|
||||||
self.get_argument("language"), self.helper.get_setting("language")
|
|
||||||
)
|
|
||||||
|
|
||||||
if superuser:
|
|
||||||
# Checks if user is trying to change super user status of self.
|
|
||||||
# We don't want that. Automatically make them stay super user
|
|
||||||
# since we know they are.
|
|
||||||
if str(exec_user["user_id"]) != str(user_id):
|
|
||||||
superuser = int(bleach.clean(self.get_argument("superuser", "0")))
|
|
||||||
else:
|
|
||||||
superuser = 1
|
|
||||||
else:
|
|
||||||
superuser = 0
|
|
||||||
|
|
||||||
if exec_user["superuser"]:
|
|
||||||
manager = self.get_argument("manager")
|
|
||||||
if manager == "":
|
|
||||||
manager = None
|
|
||||||
else:
|
|
||||||
manager = int(manager)
|
|
||||||
else:
|
|
||||||
manager = user["manager"]
|
|
||||||
|
|
||||||
if (
|
|
||||||
not exec_user["superuser"]
|
|
||||||
and int(exec_user["user_id"]) != user["manager"]
|
|
||||||
):
|
|
||||||
if username is None or username == "":
|
|
||||||
self.redirect("/panel/error?error=Invalid username")
|
|
||||||
return
|
|
||||||
if user_id is None:
|
|
||||||
self.redirect("/panel/error?error=Invalid User ID")
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
EnumPermissionsCrafty.USER_CONFIG
|
|
||||||
not in exec_user_crafty_permissions
|
|
||||||
):
|
|
||||||
if str(user_id) != str(exec_user["user_id"]):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: not a user editor"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
user_data = {
|
|
||||||
"username": username,
|
|
||||||
"password": password0,
|
|
||||||
"email": email,
|
|
||||||
"lang": lang,
|
|
||||||
"hints": hints,
|
|
||||||
"theme": theme,
|
|
||||||
}
|
|
||||||
self.controller.users.update_user(user_id, user_data=user_data)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Edited user {username} (UID:{user_id}) password",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.redirect("/panel/panel_config")
|
|
||||||
return
|
|
||||||
# does this user id exist?
|
|
||||||
if not self.controller.users.user_id_exists(user_id):
|
|
||||||
self.redirect("/panel/error?error=Invalid User ID")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
if password0 != password1:
|
|
||||||
self.redirect("/panel/error?error=Passwords must match")
|
|
||||||
return
|
|
||||||
|
|
||||||
roles = self.get_user_role_memberships()
|
|
||||||
permissions_mask, server_quantity = self.get_perms_quantity()
|
|
||||||
|
|
||||||
# if email is None or "":
|
|
||||||
# email = "default@example.com"
|
|
||||||
|
|
||||||
user_data = {
|
|
||||||
"username": username,
|
|
||||||
"manager": manager,
|
|
||||||
"password": password0,
|
|
||||||
"email": email,
|
|
||||||
"enabled": enabled,
|
|
||||||
"roles": roles,
|
|
||||||
"lang": lang,
|
|
||||||
"superuser": superuser,
|
|
||||||
"hints": hints,
|
|
||||||
"theme": theme,
|
|
||||||
}
|
|
||||||
user_crafty_data = {
|
|
||||||
"permissions_mask": permissions_mask,
|
|
||||||
"server_quantity": server_quantity,
|
|
||||||
}
|
|
||||||
self.controller.users.update_user(
|
|
||||||
user_id, user_data=user_data, user_crafty_data=user_crafty_data
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Edited user {username} (UID:{user_id}) with roles {roles} "
|
|
||||||
f"and permissions {permissions_mask}",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.redirect("/panel/panel_config")
|
|
||||||
|
|
||||||
elif page == "edit_user_apikeys":
|
|
||||||
user_id = self.get_argument("id", None)
|
|
||||||
name = self.get_argument("name", None)
|
|
||||||
superuser = self.get_argument("superuser", None) == "1"
|
|
||||||
|
|
||||||
if name is None or name == "":
|
|
||||||
self.redirect("/panel/error?error=Invalid API key name")
|
|
||||||
return
|
|
||||||
if user_id is None:
|
|
||||||
self.redirect("/panel/error?error=Invalid User ID")
|
|
||||||
return
|
|
||||||
# does this user id exist?
|
|
||||||
if not self.controller.users.user_id_exists(user_id):
|
|
||||||
self.redirect("/panel/error?error=Invalid User ID")
|
|
||||||
return
|
|
||||||
|
|
||||||
if str(user_id) != str(exec_user["user_id"]) and not exec_user["superuser"]:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=You do not have access to change"
|
|
||||||
+ "this user's api key."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
crafty_permissions_mask = self.get_perms()
|
|
||||||
server_permissions_mask = self.get_perms_server()
|
|
||||||
|
|
||||||
self.controller.users.add_user_api_key(
|
|
||||||
name,
|
|
||||||
user_id,
|
|
||||||
superuser,
|
|
||||||
server_permissions_mask,
|
|
||||||
crafty_permissions_mask,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Added API key {name} with crafty permissions "
|
|
||||||
f"{crafty_permissions_mask}"
|
|
||||||
f" and {server_permissions_mask} for user with UID: {user_id}",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.redirect(f"/panel/edit_user_apikeys?id={user_id}")
|
|
||||||
|
|
||||||
elif page == "get_token":
|
|
||||||
key_id = self.get_argument("id", None)
|
|
||||||
|
|
||||||
if key_id is None:
|
|
||||||
self.redirect("/panel/error?error=Invalid Key ID")
|
|
||||||
return
|
|
||||||
key = self.controller.users.get_user_api_key(key_id)
|
|
||||||
# does this user id exist?
|
|
||||||
if key is None:
|
|
||||||
self.redirect("/panel/error?error=Invalid Key ID")
|
|
||||||
return
|
|
||||||
|
|
||||||
if (
|
|
||||||
str(key.user_id) != str(exec_user["user_id"])
|
|
||||||
and not exec_user["superuser"]
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=You are not authorized to access this key."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Generated a new API token for the key {key.name} "
|
|
||||||
f"from user with UID: {key.user_id}",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.write(
|
|
||||||
self.controller.authentication.generate(
|
|
||||||
key.user_id_id, {"token_id": key.token_id}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
elif page == "add_user":
|
|
||||||
username = bleach.clean(self.get_argument("username", None).lower())
|
|
||||||
if username.lower() == "system":
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: "
|
|
||||||
"username system is reserved for the Crafty system."
|
|
||||||
" Please choose a different username."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
password0 = bleach.clean(self.get_argument("password0", None))
|
|
||||||
password1 = bleach.clean(self.get_argument("password1", None))
|
|
||||||
email = bleach.clean(self.get_argument("email", "default@example.com"))
|
|
||||||
enabled = int(float(self.get_argument("enabled", "0")))
|
|
||||||
theme = bleach.clean(self.get_argument("theme"), "default")
|
|
||||||
hints = True
|
|
||||||
lang = bleach.clean(
|
|
||||||
self.get_argument("lang", self.helper.get_setting("language"))
|
|
||||||
)
|
|
||||||
# We don't want a non-super user to be able to create a super user.
|
|
||||||
if superuser:
|
|
||||||
new_superuser = int(bleach.clean(self.get_argument("superuser", "0")))
|
|
||||||
else:
|
|
||||||
new_superuser = 0
|
|
||||||
|
|
||||||
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: not a user editor"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (
|
|
||||||
not self.controller.crafty_perms.can_add_user(exec_user["user_id"])
|
|
||||||
and not exec_user["superuser"]
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: quantity limit reached"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if username is None or username == "":
|
|
||||||
self.redirect("/panel/error?error=Invalid username")
|
|
||||||
return
|
|
||||||
|
|
||||||
if exec_user["superuser"]:
|
|
||||||
manager = self.get_argument("manager")
|
|
||||||
if manager == "":
|
|
||||||
manager = None
|
|
||||||
else:
|
|
||||||
manager = int(manager)
|
|
||||||
else:
|
|
||||||
manager = int(exec_user["user_id"])
|
|
||||||
# does this user id exist?
|
|
||||||
if self.controller.users.get_id_by_name(username) is not None:
|
|
||||||
self.redirect("/panel/error?error=User exists")
|
|
||||||
return
|
|
||||||
|
|
||||||
if password0 != password1:
|
|
||||||
self.redirect("/panel/error?error=Passwords must match")
|
|
||||||
return
|
|
||||||
|
|
||||||
roles = self.get_user_role_memberships()
|
|
||||||
permissions_mask, server_quantity = self.get_perms_quantity()
|
|
||||||
|
|
||||||
user_id = self.controller.users.add_user(
|
|
||||||
username,
|
|
||||||
manager=manager,
|
|
||||||
password=password0,
|
|
||||||
email=email,
|
|
||||||
enabled=enabled,
|
|
||||||
superuser=new_superuser,
|
|
||||||
theme=theme,
|
|
||||||
)
|
|
||||||
user_data = {"roles": roles, "lang": lang, "hints": True}
|
|
||||||
user_crafty_data = {
|
|
||||||
"permissions_mask": permissions_mask,
|
|
||||||
"server_quantity": server_quantity,
|
|
||||||
}
|
|
||||||
self.controller.users.update_user(
|
|
||||||
user_id, user_data=user_data, user_crafty_data=user_crafty_data
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Added user {username} (UID:{user_id})",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Edited user {username} (UID:{user_id}) with roles {roles}",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.redirect("/panel/panel_config")
|
|
||||||
|
|
||||||
elif page == "edit_role":
|
|
||||||
role_id = bleach.clean(self.get_argument("id", None))
|
|
||||||
role_name = bleach.clean(self.get_argument("role_name", None))
|
|
||||||
|
|
||||||
role = self.controller.roles.get_role(role_id)
|
|
||||||
|
|
||||||
if (
|
|
||||||
EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions
|
|
||||||
and exec_user["user_id"] != role["manager"]
|
|
||||||
and not exec_user["superuser"]
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: not a role editor"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if role_name is None or role_name == "":
|
|
||||||
self.redirect("/panel/error?error=Invalid username")
|
|
||||||
return
|
|
||||||
if role_id is None:
|
|
||||||
self.redirect("/panel/error?error=Invalid Role ID")
|
|
||||||
return
|
|
||||||
# does this user id exist?
|
|
||||||
if not self.controller.roles.role_id_exists(role_id):
|
|
||||||
self.redirect("/panel/error?error=Invalid Role ID")
|
|
||||||
return
|
|
||||||
|
|
||||||
if exec_user["superuser"]:
|
|
||||||
manager = self.get_argument("manager", None)
|
|
||||||
if manager == "":
|
|
||||||
manager = None
|
|
||||||
else:
|
|
||||||
manager = role["manager"]
|
|
||||||
|
|
||||||
servers = self.get_role_servers()
|
|
||||||
|
|
||||||
self.controller.roles.update_role_advanced(
|
|
||||||
role_id, role_name, servers, manager
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"edited role {role_name} (RID:{role_id}) with servers {servers}",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.redirect("/panel/panel_config")
|
|
||||||
|
|
||||||
elif page == "add_role":
|
|
||||||
role_name = bleach.clean(self.get_argument("role_name", None))
|
|
||||||
if exec_user["superuser"]:
|
|
||||||
manager = self.get_argument("manager", None)
|
|
||||||
if manager == "":
|
|
||||||
manager = None
|
|
||||||
else:
|
|
||||||
manager = exec_user["user_id"]
|
|
||||||
|
|
||||||
if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: not a role editor"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
not self.controller.crafty_perms.can_add_role(exec_user["user_id"])
|
|
||||||
and not exec_user["superuser"]
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: quantity limit reached"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if role_name is None or role_name == "":
|
|
||||||
self.redirect("/panel/error?error=Invalid role name")
|
|
||||||
return
|
|
||||||
# does this user id exist?
|
|
||||||
if self.controller.roles.get_roleid_by_name(role_name) is not None:
|
|
||||||
self.redirect("/panel/error?error=Role exists")
|
|
||||||
return
|
|
||||||
|
|
||||||
servers = self.get_role_servers()
|
|
||||||
|
|
||||||
role_id = self.controller.roles.add_role_advanced(
|
|
||||||
role_name, servers, manager
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"created role {role_name} (RID:{role_id})",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.redirect("/panel/panel_config")
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.set_status(404)
|
|
||||||
page_data = {
|
|
||||||
"lang": self.helper.get_setting("language"),
|
|
||||||
"lang_page": Helpers.get_lang_page(self.helper.get_setting("language")),
|
|
||||||
}
|
|
||||||
self.render(
|
|
||||||
"public/404.html", translate=self.translator.translate, data=page_data
|
|
||||||
)
|
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def delete(self, page):
|
|
||||||
api_key, _token_data, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
page_data = {
|
|
||||||
# todo: make this actually pull and compare version data
|
|
||||||
"update_available": False,
|
|
||||||
"version_data": self.helper.get_version_string(),
|
|
||||||
"user_data": exec_user,
|
|
||||||
"hosts_data": self.controller.management.get_latest_hosts_stats(),
|
|
||||||
"show_contribute": self.helper.get_setting("show_contribute_link", True),
|
|
||||||
"lang": self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
|
|
||||||
"lang_page": Helpers.get_lang_page(
|
|
||||||
self.controller.users.get_user_lang_by_id(exec_user["user_id"])
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
if page == "remove_apikey":
|
|
||||||
key_id = bleach.clean(self.get_argument("id", None))
|
|
||||||
|
|
||||||
if not superuser:
|
|
||||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
|
||||||
return
|
|
||||||
if key_id is None or self.controller.users.get_user_api_key(key_id) is None:
|
|
||||||
self.redirect("/panel/error?error=Invalid Key ID")
|
|
||||||
return
|
|
||||||
# does this user id exist?
|
|
||||||
target_key = self.controller.users.get_user_api_key(key_id)
|
|
||||||
if not target_key:
|
|
||||||
self.redirect("/panel/error?error=Invalid Key ID")
|
|
||||||
return
|
|
||||||
|
|
||||||
key_obj = self.controller.users.get_user_api_key(key_id)
|
|
||||||
|
|
||||||
if key_obj.user_id != exec_user["user_id"] and not exec_user["superuser"]:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=You do not have access to change"
|
|
||||||
+ "this user's api key."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.controller.users.delete_user_api_key(key_id)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"Removed API key {target_key} "
|
|
||||||
f"(ID: {key_id}) from user {exec_user['user_id']}",
|
|
||||||
server_id=0,
|
|
||||||
source_ip=self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.finish()
|
|
||||||
self.redirect(f"/panel/edit_user_apikeys?id={key_obj.user_id}")
|
|
||||||
else:
|
|
||||||
self.set_status(404)
|
|
||||||
self.render(
|
|
||||||
"public/404.html",
|
|
||||||
data=page_data,
|
|
||||||
translate=self.translator.translate,
|
|
||||||
)
|
|
||||||
|
@ -26,6 +26,17 @@ from app.classes.web.routes.api.servers.server.stdin import ApiServersServerStdi
|
|||||||
from app.classes.web.routes.api.servers.server.tasks.index import (
|
from app.classes.web.routes.api.servers.server.tasks.index import (
|
||||||
ApiServersServerTasksIndexHandler,
|
ApiServersServerTasksIndexHandler,
|
||||||
)
|
)
|
||||||
|
from app.classes.web.routes.api.servers.server.backups.index import (
|
||||||
|
ApiServersServerBackupsIndexHandler,
|
||||||
|
)
|
||||||
|
from app.classes.web.routes.api.servers.server.backups.backup.index import (
|
||||||
|
ApiServersServerBackupsBackupIndexHandler,
|
||||||
|
)
|
||||||
|
from app.classes.web.routes.api.servers.server.files import (
|
||||||
|
ApiServersServerFilesIndexHandler,
|
||||||
|
ApiServersServerFilesCreateHandler,
|
||||||
|
ApiServersServerFilesZipHandler,
|
||||||
|
)
|
||||||
from app.classes.web.routes.api.servers.server.tasks.task.children import (
|
from app.classes.web.routes.api.servers.server.tasks.task.children import (
|
||||||
ApiServersServerTasksTaskChildrenHandler,
|
ApiServersServerTasksTaskChildrenHandler,
|
||||||
)
|
)
|
||||||
@ -44,8 +55,22 @@ from app.classes.web.routes.api.users.user.index import ApiUsersUserIndexHandler
|
|||||||
from app.classes.web.routes.api.users.user.permissions import (
|
from app.classes.web.routes.api.users.user.permissions import (
|
||||||
ApiUsersUserPermissionsHandler,
|
ApiUsersUserPermissionsHandler,
|
||||||
)
|
)
|
||||||
|
from app.classes.web.routes.api.users.user.api import ApiUsersUserKeyHandler
|
||||||
from app.classes.web.routes.api.users.user.pfp import ApiUsersUserPfpHandler
|
from app.classes.web.routes.api.users.user.pfp import ApiUsersUserPfpHandler
|
||||||
from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandler
|
from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandler
|
||||||
|
from app.classes.web.routes.api.crafty.announcements.index import (
|
||||||
|
ApiAnnounceIndexHandler,
|
||||||
|
)
|
||||||
|
from app.classes.web.routes.api.crafty.config.index import (
|
||||||
|
ApiCraftyConfigIndexHandler,
|
||||||
|
ApiCraftyCustomizeIndexHandler,
|
||||||
|
)
|
||||||
|
from app.classes.web.routes.api.crafty.config.server_dir import (
|
||||||
|
ApiCraftyConfigServerDirHandler,
|
||||||
|
)
|
||||||
|
from app.classes.web.routes.api.crafty.clogs.index import ApiCraftyLogIndexHandler
|
||||||
|
from app.classes.web.routes.api.crafty.imports.index import ApiImportFilesIndexHandler
|
||||||
|
from app.classes.web.routes.api.crafty.exe_cache import ApiCraftyExeCacheIndexHandler
|
||||||
|
|
||||||
|
|
||||||
def api_handlers(handler_args):
|
def api_handlers(handler_args):
|
||||||
@ -61,12 +86,52 @@ def api_handlers(handler_args):
|
|||||||
ApiAuthInvalidateTokensHandler,
|
ApiAuthInvalidateTokensHandler,
|
||||||
handler_args,
|
handler_args,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/crafty/announcements/?",
|
||||||
|
ApiAnnounceIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/crafty/config/?",
|
||||||
|
ApiCraftyConfigIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/crafty/config/customize/?",
|
||||||
|
ApiCraftyCustomizeIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/crafty/config/servers_dir/?",
|
||||||
|
ApiCraftyConfigServerDirHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/crafty/logs/([a-z0-9_]+)/?",
|
||||||
|
ApiCraftyLogIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/import/file/unzip/?",
|
||||||
|
ApiImportFilesIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
# User routes
|
# User routes
|
||||||
(
|
(
|
||||||
r"/api/v2/users/?",
|
r"/api/v2/users/?",
|
||||||
ApiUsersIndexHandler,
|
ApiUsersIndexHandler,
|
||||||
handler_args,
|
handler_args,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/users/([0-9]+)/key/?",
|
||||||
|
ApiUsersUserKeyHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/users/([0-9]+)/key/([0-9]+)/?",
|
||||||
|
ApiUsersUserKeyHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
r"/api/v2/users/([0-9]+)/?",
|
r"/api/v2/users/([0-9]+)/?",
|
||||||
ApiUsersUserIndexHandler,
|
ApiUsersUserIndexHandler,
|
||||||
@ -113,11 +178,41 @@ def api_handlers(handler_args):
|
|||||||
ApiServersIndexHandler,
|
ApiServersIndexHandler,
|
||||||
handler_args,
|
handler_args,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/crafty/exeCache/?",
|
||||||
|
ApiCraftyExeCacheIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
r"/api/v2/servers/([0-9]+)/?",
|
r"/api/v2/servers/([0-9]+)/?",
|
||||||
ApiServersServerIndexHandler,
|
ApiServersServerIndexHandler,
|
||||||
handler_args,
|
handler_args,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/servers/([0-9]+)/backups/?",
|
||||||
|
ApiServersServerBackupsIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/servers/([0-9]+)/backups/backup/?",
|
||||||
|
ApiServersServerBackupsBackupIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/servers/([0-9]+)/files/?",
|
||||||
|
ApiServersServerFilesIndexHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/servers/([0-9]+)/files/create/?",
|
||||||
|
ApiServersServerFilesCreateHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/servers/([0-9]+)/files/zip/?",
|
||||||
|
ApiServersServerFilesZipHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
r"/api/v2/servers/([0-9]+)/tasks/?",
|
r"/api/v2/servers/([0-9]+)/tasks/?",
|
||||||
ApiServersServerTasksIndexHandler,
|
ApiServersServerTasksIndexHandler,
|
||||||
|
110
app/classes/web/routes/api/crafty/announcements/index.py
Normal file
110
app/classes/web/routes/api/crafty/announcements/index.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from jsonschema import ValidationError, validate
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
notif_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ApiAnnounceIndexHandler(BaseApiHandler):
|
||||||
|
def get(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_exec_user_crafty_permissions,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_user,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
data = self.helper.get_announcements()
|
||||||
|
cleared = str(
|
||||||
|
self.controller.users.get_user_by_id(auth_data[4]["user_id"])[
|
||||||
|
"cleared_notifs"
|
||||||
|
]
|
||||||
|
).split(",")
|
||||||
|
res = [d.get("id", None) for d in data]
|
||||||
|
# remove notifs that are no longer in Crafty.
|
||||||
|
for item in cleared[:]:
|
||||||
|
if item not in res:
|
||||||
|
cleared.remove(item)
|
||||||
|
updata = {"cleared_notifs": ",".join(cleared)}
|
||||||
|
self.controller.users.update_user(auth_data[4]["user_id"], updata)
|
||||||
|
if len(cleared) > 0:
|
||||||
|
for item in data[:]:
|
||||||
|
if item["id"] in cleared:
|
||||||
|
data.remove(item)
|
||||||
|
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": data,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_exec_user_crafty_permissions,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_user,
|
||||||
|
) = auth_data
|
||||||
|
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, notif_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
announcements = self.helper.get_announcements()
|
||||||
|
res = [d.get("id", None) for d in announcements]
|
||||||
|
cleared_notifs = str(
|
||||||
|
self.controller.users.get_user_by_id(auth_data[4]["user_id"])[
|
||||||
|
"cleared_notifs"
|
||||||
|
]
|
||||||
|
).split(",")
|
||||||
|
# remove notifs that are no longer in Crafty.
|
||||||
|
for item in cleared_notifs[:]:
|
||||||
|
if item not in res:
|
||||||
|
cleared_notifs.remove(item)
|
||||||
|
if str(data["id"]) in str(res):
|
||||||
|
cleared_notifs.append(data["id"])
|
||||||
|
else:
|
||||||
|
self.finish_json(200, {"status": "error", "error": "INVALID_DATA"})
|
||||||
|
return
|
||||||
|
updata = {"cleared_notifs": ",".join(cleared_notifs)}
|
||||||
|
self.controller.users.update_user(auth_data[4]["user_id"], updata)
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": {},
|
||||||
|
},
|
||||||
|
)
|
34
app/classes/web/routes/api/crafty/clogs/index.py
Normal file
34
app/classes/web/routes/api/crafty/clogs/index.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
|
||||||
|
class ApiCraftyLogIndexHandler(BaseApiHandler):
|
||||||
|
def get(self, log_type: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
superuser,
|
||||||
|
_,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
if not superuser:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
log_types = ["audit", "session", "schedule"]
|
||||||
|
if log_type not in log_types:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
if log_type == "audit":
|
||||||
|
return self.finish_json(
|
||||||
|
200,
|
||||||
|
{"status": "ok", "data": self.controller.management.get_activity_log()},
|
||||||
|
)
|
||||||
|
|
||||||
|
if log_type == "session":
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
if log_type == "schedule":
|
||||||
|
raise NotImplementedError
|
312
app/classes/web/routes/api/crafty/config/index.py
Normal file
312
app/classes/web/routes/api/crafty/config/index.py
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from jsonschema import ValidationError, validate
|
||||||
|
import orjson
|
||||||
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
from app.classes.shared.file_helpers import FileHelpers
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
config_json_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"http_port": {"type": "integer"},
|
||||||
|
"https_port": {"type": "integer"},
|
||||||
|
"language": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"cookie_expire": {"type": "integer"},
|
||||||
|
"show_errors": {"type": "boolean"},
|
||||||
|
"history_max_age": {"type": "integer"},
|
||||||
|
"stats_update_frequency_seconds": {"type": "integer"},
|
||||||
|
"delete_default_json": {"type": "boolean"},
|
||||||
|
"show_contribute_link": {"type": "boolean"},
|
||||||
|
"virtual_terminal_lines": {"type": "integer"},
|
||||||
|
"max_log_lines": {"type": "integer"},
|
||||||
|
"max_audit_entries": {"type": "integer"},
|
||||||
|
"disabled_language_files": {"type": "array"},
|
||||||
|
"stream_size_GB": {"type": "integer"},
|
||||||
|
"keywords": {"type": "array"},
|
||||||
|
"allow_nsfw_profile_pictures": {"type": "boolean"},
|
||||||
|
"enable_user_self_delete": {"type": "boolean"},
|
||||||
|
"reset_secrets_on_next_boot": {"type": "boolean"},
|
||||||
|
"monitored_mounts": {"type": "array"},
|
||||||
|
"dir_size_poll_freq_minutes": {"type": "integer"},
|
||||||
|
"crafty_logs_delete_after_days": {"type": "integer"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
customize_json_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"photo": {"type": "string"},
|
||||||
|
"opacity": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
photo_delete_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"photo": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
DEFAULT_PHOTO = "login_1.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
class ApiCraftyConfigIndexHandler(BaseApiHandler):
|
||||||
|
def get(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
superuser,
|
||||||
|
_,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
# GET /api/v2/roles?ids=true
|
||||||
|
get_only_ids = self.get_query_argument("ids", None) == "true"
|
||||||
|
|
||||||
|
if not superuser:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": self.controller.roles.get_all_role_ids()
|
||||||
|
if get_only_ids
|
||||||
|
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def patch(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
superuser,
|
||||||
|
user,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
if not superuser:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = orjson.loads(self.request.body)
|
||||||
|
except orjson.decoder.JSONDecodeError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate(data, config_json_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.controller.set_config_json(data)
|
||||||
|
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
user["user_id"],
|
||||||
|
"edited config.json",
|
||||||
|
server_id=0,
|
||||||
|
source_ip=self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{"status": "ok"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
|
||||||
|
def get(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
superuser,
|
||||||
|
_,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
# GET /api/v2/roles?ids=true
|
||||||
|
get_only_ids = self.get_query_argument("ids", None) == "true"
|
||||||
|
|
||||||
|
if not superuser:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": self.controller.roles.get_all_role_ids()
|
||||||
|
if get_only_ids
|
||||||
|
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def patch(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
superuser,
|
||||||
|
user,
|
||||||
|
) = auth_data
|
||||||
|
if not superuser:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = orjson.loads(self.request.body)
|
||||||
|
except orjson.decoder.JSONDecodeError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate(data, customize_json_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not self.helper.validate_traversal(
|
||||||
|
os.path.join(
|
||||||
|
self.controller.project_root,
|
||||||
|
"app/frontend/static/assets/images/auth/",
|
||||||
|
),
|
||||||
|
os.path.join(
|
||||||
|
self.controller.project_root,
|
||||||
|
f"app/frontend/static/assets/images/auth/{data['photo']}",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": "TRIED TO REACH FILES THAT ARE"
|
||||||
|
" NOT SUPPOSED TO BE ACCESSIBLE",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
user["user_id"],
|
||||||
|
f"customized login photo: {data['photo']}/{data['opacity']}",
|
||||||
|
server_id=0,
|
||||||
|
source_ip=self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
self.controller.management.set_login_opacity(int(data["opacity"]))
|
||||||
|
if data["photo"] == DEFAULT_PHOTO:
|
||||||
|
self.controller.management.set_login_image(DEFAULT_PHOTO)
|
||||||
|
self.controller.cached_login = f"{data['photo']}"
|
||||||
|
else:
|
||||||
|
self.controller.management.set_login_image(f"custom/{data['photo']}")
|
||||||
|
self.controller.cached_login = f"custom/{data['photo']}"
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": {"photo": data["photo"], "opacity": data["opacity"]},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not auth_data[4]["superuser"]:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
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, photo_delete_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not self.helper.validate_traversal(
|
||||||
|
os.path.join(
|
||||||
|
self.controller.project_root,
|
||||||
|
"app",
|
||||||
|
"frontend",
|
||||||
|
"/static/assets/images/auth/",
|
||||||
|
),
|
||||||
|
os.path.join(
|
||||||
|
self.controller.project_root,
|
||||||
|
"app",
|
||||||
|
"frontend",
|
||||||
|
"/static/assets/images/auth/",
|
||||||
|
data["photo"],
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": "TRIED TO REACH FILES THAT ARE"
|
||||||
|
" NOT SUPPOSED TO BE ACCESSIBLE",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if data["photo"] == DEFAULT_PHOTO:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID FILE",
|
||||||
|
"error_data": "CANNOT DELETE DEFAULT",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
FileHelpers.del_file(
|
||||||
|
os.path.join(
|
||||||
|
self.controller.project_root,
|
||||||
|
f"app/frontend/static/assets/images/auth/custom/{data['photo']}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
current = self.controller.cached_login
|
||||||
|
split = current.split("/")
|
||||||
|
if len(split) == 1:
|
||||||
|
current_photo = current
|
||||||
|
else:
|
||||||
|
current_photo = split[1]
|
||||||
|
if current_photo == data["photo"]:
|
||||||
|
self.controller.management.set_login_image(DEFAULT_PHOTO)
|
||||||
|
self.controller.cached_login = DEFAULT_PHOTO
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
115
app/classes/web/routes/api/crafty/config/server_dir.py
Normal file
115
app/classes/web/routes/api/crafty/config/server_dir.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
from jsonschema import ValidationError, validate
|
||||||
|
import orjson
|
||||||
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
server_dir_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"new_dir": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ApiCraftyConfigServerDirHandler(BaseApiHandler):
|
||||||
|
def get(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
superuser,
|
||||||
|
_,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
# GET /api/v2/roles?ids=true
|
||||||
|
get_only_ids = self.get_query_argument("ids", None) == "true"
|
||||||
|
|
||||||
|
if not superuser:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": self.controller.roles.get_all_role_ids()
|
||||||
|
if get_only_ids
|
||||||
|
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def patch(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
if not auth_data:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
if not auth_data[4]["superuser"]:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
if self.helper.is_env_docker():
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = orjson.loads(self.request.body)
|
||||||
|
except orjson.decoder.JSONDecodeError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate(data, server_dir_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if self.helper.dir_migration:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "IN PROGRESS",
|
||||||
|
"error_data": "Migration already in progress. Please be patient",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for server in self.controller.servers.get_all_servers_stats():
|
||||||
|
if server["stats"]["running"]:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "SERVER RUNNING",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
new_dir = data["new_dir"]
|
||||||
|
self.controller.update_master_server_dir(new_dir, auth_data[4]["user_id"])
|
||||||
|
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
auth_data[4]["user_id"],
|
||||||
|
f"updated master servers dir to {new_dir}/servers",
|
||||||
|
server_id=0,
|
||||||
|
source_ip=self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{"status": "ok"},
|
||||||
|
)
|
27
app/classes/web/routes/api/crafty/exe_cache.py
Normal file
27
app/classes/web/routes/api/crafty/exe_cache.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
|
||||||
|
class ApiCraftyExeCacheIndexHandler(BaseApiHandler):
|
||||||
|
def get(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
if not auth_data[4]["superuser"]:
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
self.controller.server_jars.manual_refresh_cache()
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": self.controller.server_jars.get_serverjar_data(),
|
||||||
|
},
|
||||||
|
)
|
128
app/classes/web/routes/api/crafty/imports/index.py
Normal file
128
app/classes/web/routes/api/crafty/imports/index.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import html
|
||||||
|
from jsonschema import validate
|
||||||
|
from jsonschema.exceptions import ValidationError
|
||||||
|
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||||
|
from app.classes.shared.helpers import Helpers
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
files_get_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"page": {"type": "string", "minLength": 1},
|
||||||
|
"folder": {"type": "string"},
|
||||||
|
"upload": {"type": "boolean", "default": "False"},
|
||||||
|
"unzip": {"type": "boolean", "default": "True"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ApiImportFilesIndexHandler(BaseApiHandler):
|
||||||
|
def post(self):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
EnumPermissionsCrafty.SERVER_CREATION
|
||||||
|
not in self.controller.crafty_perms.get_crafty_permissions_list(
|
||||||
|
auth_data[4]["user_id"]
|
||||||
|
)
|
||||||
|
and not auth_data[4]["superuser"]
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files or Backup permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
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, files_get_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# TODO: limit some columns for specific permissions?
|
||||||
|
folder = data["folder"]
|
||||||
|
user_id = auth_data[4]["user_id"]
|
||||||
|
root_path = False
|
||||||
|
if data["unzip"]:
|
||||||
|
# This is awful. Once uploads go to return
|
||||||
|
# JSON we need to remove this and just send
|
||||||
|
# the path.
|
||||||
|
if data["upload"]:
|
||||||
|
folder = os.path.join(self.controller.project_root, "imports", folder)
|
||||||
|
if Helpers.check_file_exists(folder):
|
||||||
|
folder = self.file_helper.unzip_server(folder, user_id)
|
||||||
|
root_path = True
|
||||||
|
else:
|
||||||
|
if user_id:
|
||||||
|
user_lang = self.controller.users.get_user_lang_by_id(user_id)
|
||||||
|
self.helper.websocket_helper.broadcast_user(
|
||||||
|
user_id,
|
||||||
|
"send_start_error",
|
||||||
|
{
|
||||||
|
"error": self.helper.translation.translate(
|
||||||
|
"error", "no-file", user_lang
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if not self.helper.check_path_exists(folder) and user_id:
|
||||||
|
user_lang = self.controller.users.get_user_lang_by_id(user_id)
|
||||||
|
self.helper.websocket_helper.broadcast_user(
|
||||||
|
user_id,
|
||||||
|
"send_start_error",
|
||||||
|
{
|
||||||
|
"error": self.helper.translation.translate(
|
||||||
|
"error", "no-file", user_lang
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return_json = {
|
||||||
|
"root_path": {
|
||||||
|
"path": folder,
|
||||||
|
"top": root_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir_list = []
|
||||||
|
unsorted_files = []
|
||||||
|
file_list = os.listdir(folder)
|
||||||
|
for item in file_list:
|
||||||
|
if os.path.isdir(os.path.join(folder, item)):
|
||||||
|
dir_list.append(item)
|
||||||
|
else:
|
||||||
|
unsorted_files.append(item)
|
||||||
|
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||||
|
unsorted_files, key=str.casefold
|
||||||
|
)
|
||||||
|
for raw_filename in file_list:
|
||||||
|
filename = html.escape(raw_filename)
|
||||||
|
rel = os.path.join(folder, raw_filename)
|
||||||
|
dpath = os.path.join(folder, filename)
|
||||||
|
dpath = self.helper.wtol_path(dpath)
|
||||||
|
if os.path.isdir(rel):
|
||||||
|
return_json[filename] = {
|
||||||
|
"path": dpath,
|
||||||
|
"dir": True,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return_json[filename] = {
|
||||||
|
"path": dpath,
|
||||||
|
"dir": False,
|
||||||
|
}
|
||||||
|
self.finish_json(200, {"status": "ok", "data": return_json})
|
@ -28,9 +28,39 @@ create_role_schema = {
|
|||||||
"required": ["server_id", "permissions"],
|
"required": ["server_id", "permissions"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"manager": {"type": ["integer", "null"]},
|
||||||
},
|
},
|
||||||
"required": ["name"],
|
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
basic_create_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"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -86,7 +116,10 @@ class ApiRolesIndexHandler(BaseApiHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validate(data, create_role_schema)
|
if auth_data[4]["superuser"]:
|
||||||
|
validate(data, create_role_schema)
|
||||||
|
else:
|
||||||
|
validate(data, basic_create_role_schema)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
400,
|
400,
|
||||||
@ -98,6 +131,9 @@ class ApiRolesIndexHandler(BaseApiHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
role_name = data["name"]
|
role_name = data["name"]
|
||||||
|
manager = data.get("manager", None)
|
||||||
|
if manager == self.controller.users.get_id_by_name("SYSTEM") or manager == 0:
|
||||||
|
manager = None
|
||||||
|
|
||||||
# Get the servers
|
# Get the servers
|
||||||
servers_dict = {server["server_id"]: server for server in data["servers"]}
|
servers_dict = {server["server_id"]: server for server in data["servers"]}
|
||||||
@ -116,9 +152,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
|
|||||||
400, {"status": "error", "error": "ROLE_NAME_ALREADY_EXISTS"}
|
400, {"status": "error", "error": "ROLE_NAME_ALREADY_EXISTS"}
|
||||||
)
|
)
|
||||||
|
|
||||||
role_id = self.controller.roles.add_role_advanced(
|
role_id = self.controller.roles.add_role_advanced(role_name, servers, manager)
|
||||||
role_name, servers, user["user_id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.management.add_to_audit_log(
|
self.controller.management.add_to_audit_log(
|
||||||
user["user_id"],
|
user["user_id"],
|
||||||
|
@ -153,9 +153,18 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
manager = data.get(
|
||||||
|
"manager", self.controller.roles.get_role(role_id)["manager"]
|
||||||
|
)
|
||||||
|
if manager == self.controller.users.get_id_by_name("system") or manager == 0:
|
||||||
|
manager = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.controller.roles.update_role_advanced(
|
self.controller.roles.update_role_advanced(
|
||||||
role_id, data.get("role_name", None), data.get("servers", None)
|
role_id,
|
||||||
|
data.get("name", None),
|
||||||
|
data.get("servers", None),
|
||||||
|
manager,
|
||||||
)
|
)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
|
return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
|
||||||
|
@ -24,6 +24,7 @@ new_server_schema = {
|
|||||||
"examples": ["My Server"],
|
"examples": ["My Server"],
|
||||||
"minLength": 2,
|
"minLength": 2,
|
||||||
},
|
},
|
||||||
|
"roles": {"title": "Roles to add", "type": "array", "examples": [1, 2, 3]},
|
||||||
"stop_command": {
|
"stop_command": {
|
||||||
"title": "Stop command",
|
"title": "Stop command",
|
||||||
"description": '"" means the default for the server creation type.',
|
"description": '"" means the default for the server creation type.',
|
||||||
@ -133,8 +134,13 @@ new_server_schema = {
|
|||||||
"mem_min",
|
"mem_min",
|
||||||
"mem_max",
|
"mem_max",
|
||||||
"server_properties_port",
|
"server_properties_port",
|
||||||
"agree_to_eula",
|
"category",
|
||||||
],
|
],
|
||||||
|
"category": {
|
||||||
|
"title": "Jar Category",
|
||||||
|
"type": "string",
|
||||||
|
"examples": ["modded", "vanilla"],
|
||||||
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Server JAR Type",
|
"title": "Server JAR Type",
|
||||||
@ -185,7 +191,6 @@ new_server_schema = {
|
|||||||
"mem_min",
|
"mem_min",
|
||||||
"mem_max",
|
"mem_max",
|
||||||
"server_properties_port",
|
"server_properties_port",
|
||||||
"agree_to_eula",
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"existing_server_path": {
|
"existing_server_path": {
|
||||||
@ -240,7 +245,6 @@ new_server_schema = {
|
|||||||
"mem_min",
|
"mem_min",
|
||||||
"mem_max",
|
"mem_max",
|
||||||
"server_properties_port",
|
"server_properties_port",
|
||||||
"agree_to_eula",
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"zip_path": {
|
"zip_path": {
|
||||||
@ -336,12 +340,24 @@ new_server_schema = {
|
|||||||
"title": "Creation type",
|
"title": "Creation type",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "import_server",
|
"default": "import_server",
|
||||||
"enum": ["import_server", "import_zip"],
|
"enum": ["download_exe", "import_server", "import_zip"],
|
||||||
|
},
|
||||||
|
"download_exe_create_data": {
|
||||||
|
"title": "Import server data",
|
||||||
|
"type": "object",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"agree_to_eula": {
|
||||||
|
"title": "Agree to the EULA",
|
||||||
|
"type": "boolean",
|
||||||
|
"enum": [True],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"import_server_create_data": {
|
"import_server_create_data": {
|
||||||
"title": "Import server data",
|
"title": "Import server data",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["existing_server_path", "command"],
|
"required": ["existing_server_path", "executable"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"existing_server_path": {
|
"existing_server_path": {
|
||||||
"title": "Server path",
|
"title": "Server path",
|
||||||
@ -350,6 +366,14 @@ new_server_schema = {
|
|||||||
"examples": ["/var/opt/server"],
|
"examples": ["/var/opt/server"],
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
},
|
},
|
||||||
|
"executable": {
|
||||||
|
"title": "Executable File",
|
||||||
|
"description": "File Crafty should execute"
|
||||||
|
"on server launch",
|
||||||
|
"type": "string",
|
||||||
|
"examples": ["bedrock_server.exe"],
|
||||||
|
"minlength": 1,
|
||||||
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"title": "Command",
|
"title": "Command",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -371,6 +395,14 @@ new_server_schema = {
|
|||||||
"examples": ["/var/opt/server.zip"],
|
"examples": ["/var/opt/server.zip"],
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
},
|
},
|
||||||
|
"executable": {
|
||||||
|
"title": "Executable File",
|
||||||
|
"description": "File Crafty should execute"
|
||||||
|
"on server launch",
|
||||||
|
"type": "string",
|
||||||
|
"examples": ["bedrock_server.exe"],
|
||||||
|
"minlength": 1,
|
||||||
|
},
|
||||||
"zip_root": {
|
"zip_root": {
|
||||||
"title": "Server root directory",
|
"title": "Server root directory",
|
||||||
"description": "The server root in the ZIP archive",
|
"description": "The server root in the ZIP archive",
|
||||||
@ -394,7 +426,9 @@ new_server_schema = {
|
|||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
"if": {
|
"if": {
|
||||||
"properties": {"create_type": {"const": "import_exec"}}
|
"properties": {
|
||||||
|
"create_type": {"const": "import_server"}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"then": {"required": ["import_server_create_data"]},
|
"then": {"required": ["import_server_create_data"]},
|
||||||
},
|
},
|
||||||
@ -404,6 +438,16 @@ new_server_schema = {
|
|||||||
},
|
},
|
||||||
"then": {"required": ["import_zip_create_data"]},
|
"then": {"required": ["import_zip_create_data"]},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {"create_type": {"const": "download_exe"}}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": [
|
||||||
|
"download_exe_create_data",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -411,6 +455,7 @@ new_server_schema = {
|
|||||||
"oneOf": [
|
"oneOf": [
|
||||||
{"required": ["import_server_create_data"]},
|
{"required": ["import_server_create_data"]},
|
||||||
{"required": ["import_zip_create_data"]},
|
{"required": ["import_zip_create_data"]},
|
||||||
|
{"required": ["download_exe_create_data"]},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -651,7 +696,6 @@ class ApiServersIndexHandler(BaseApiHandler):
|
|||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validate(data, new_server_schema)
|
validate(data, new_server_schema)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
|
@ -31,6 +31,8 @@ class ApiServersServerActionHandler(BaseApiHandler):
|
|||||||
|
|
||||||
if action == "clone_server":
|
if action == "clone_server":
|
||||||
return self._clone_server(server_id, auth_data[4]["user_id"])
|
return self._clone_server(server_id, auth_data[4]["user_id"])
|
||||||
|
if action == "eula":
|
||||||
|
return self._agree_eula(server_id, auth_data[4]["user_id"])
|
||||||
|
|
||||||
self.controller.management.send_command(
|
self.controller.management.send_command(
|
||||||
auth_data[4]["user_id"], server_id, self.get_remote_ip(), action
|
auth_data[4]["user_id"], server_id, self.get_remote_ip(), action
|
||||||
@ -41,6 +43,11 @@ class ApiServersServerActionHandler(BaseApiHandler):
|
|||||||
{"status": "ok"},
|
{"status": "ok"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _agree_eula(self, server_id, user):
|
||||||
|
svr = self.controller.servers.get_server_instance_by_id(server_id)
|
||||||
|
svr.agree_eula(user)
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
||||||
|
|
||||||
def _clone_server(self, server_id, user_id):
|
def _clone_server(self, server_id, user_id):
|
||||||
def is_name_used(name):
|
def is_name_used(name):
|
||||||
return Servers.select().where(Servers.server_name == name).exists()
|
return Servers.select().where(Servers.server_name == name).exists()
|
||||||
|
@ -0,0 +1,210 @@
|
|||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from apscheduler.jobstores.base import JobLookupError
|
||||||
|
from jsonschema import validate
|
||||||
|
from jsonschema.exceptions import ValidationError
|
||||||
|
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||||
|
from app.classes.shared.file_helpers import FileHelpers
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
from app.classes.shared.helpers import Helpers
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
backup_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"filename": {"type": "string", "minLength": 5},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
|
||||||
|
def get(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
EnumPermissionsServer.BACKUP
|
||||||
|
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_backup_config(server_id))
|
||||||
|
|
||||||
|
def delete(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
backup_conf = self.controller.management.get_backup_config(server_id)
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
EnumPermissionsServer.BACKUP
|
||||||
|
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:
|
||||||
|
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, backup_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
FileHelpers.del_file(
|
||||||
|
os.path.join(backup_conf["backup_path"], data["filename"])
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "NO BACKUP FOUND"}
|
||||||
|
)
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
auth_data[4]["user_id"],
|
||||||
|
f"Edited server {server_id}: removed backup {data['filename']}",
|
||||||
|
server_id,
|
||||||
|
self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
||||||
|
|
||||||
|
def post(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
EnumPermissionsServer.BACKUP
|
||||||
|
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:
|
||||||
|
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, backup_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
svr_obj = self.controller.servers.get_server_obj(server_id)
|
||||||
|
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
||||||
|
zip_name = data["filename"]
|
||||||
|
# import the server again based on zipfile
|
||||||
|
if server_data["type"] == "minecraft-java":
|
||||||
|
backup_path = svr_obj.backup_path
|
||||||
|
if Helpers.validate_traversal(backup_path, zip_name):
|
||||||
|
temp_dir = Helpers.unzip_backup_archive(backup_path, zip_name)
|
||||||
|
new_server = self.controller.import_zip_server(
|
||||||
|
svr_obj.server_name,
|
||||||
|
temp_dir,
|
||||||
|
server_data["executable"],
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
server_data["server_port"],
|
||||||
|
server_data["created_by"],
|
||||||
|
)
|
||||||
|
new_server_id = new_server
|
||||||
|
new_server = self.controller.servers.get_server_data(new_server)
|
||||||
|
self.controller.rename_backup_dir(
|
||||||
|
server_id, new_server_id, new_server["server_uuid"]
|
||||||
|
)
|
||||||
|
# preserve current schedules
|
||||||
|
for schedule in self.controller.management.get_schedules_by_server(
|
||||||
|
server_id
|
||||||
|
):
|
||||||
|
self.tasks_manager.update_job(
|
||||||
|
schedule.schedule_id, {"server_id": new_server_id}
|
||||||
|
)
|
||||||
|
# preserve execution command
|
||||||
|
new_server_obj = self.controller.servers.get_server_obj(
|
||||||
|
new_server_id
|
||||||
|
)
|
||||||
|
new_server_obj.execution_command = server_data["execution_command"]
|
||||||
|
# reset executable path
|
||||||
|
if svr_obj.path in svr_obj.executable:
|
||||||
|
new_server_obj.executable = str(svr_obj.executable).replace(
|
||||||
|
svr_obj.path, new_server_obj.path
|
||||||
|
)
|
||||||
|
# reset run command path
|
||||||
|
if svr_obj.path in svr_obj.execution_command:
|
||||||
|
new_server_obj.execution_command = str(
|
||||||
|
svr_obj.execution_command
|
||||||
|
).replace(svr_obj.path, new_server_obj.path)
|
||||||
|
# reset log path
|
||||||
|
if svr_obj.path in svr_obj.log_path:
|
||||||
|
new_server_obj.log_path = str(svr_obj.log_path).replace(
|
||||||
|
svr_obj.path, new_server_obj.path
|
||||||
|
)
|
||||||
|
self.controller.servers.update_server(new_server_obj)
|
||||||
|
|
||||||
|
# preserve backup config
|
||||||
|
backup_config = self.controller.management.get_backup_config(
|
||||||
|
server_id
|
||||||
|
)
|
||||||
|
excluded_dirs = []
|
||||||
|
server_obj = self.controller.servers.get_server_obj(server_id)
|
||||||
|
loop_backup_path = self.helper.wtol_path(server_obj.path)
|
||||||
|
for item in self.controller.management.get_excluded_backup_dirs(
|
||||||
|
server_id
|
||||||
|
):
|
||||||
|
item_path = self.helper.wtol_path(item)
|
||||||
|
bu_path = os.path.relpath(item_path, loop_backup_path)
|
||||||
|
bu_path = os.path.join(new_server_obj.path, bu_path)
|
||||||
|
excluded_dirs.append(bu_path)
|
||||||
|
self.controller.management.set_backup_config(
|
||||||
|
new_server_id,
|
||||||
|
new_server_obj.backup_path,
|
||||||
|
backup_config["max_backups"],
|
||||||
|
excluded_dirs,
|
||||||
|
backup_config["compress"],
|
||||||
|
backup_config["shutdown"],
|
||||||
|
)
|
||||||
|
# remove old server's tasks
|
||||||
|
try:
|
||||||
|
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||||
|
except JobLookupError as e:
|
||||||
|
logger.info("No active tasks found for server: {e}")
|
||||||
|
self.controller.remove_server(server_id, True)
|
||||||
|
except Exception:
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "NO BACKUP FOUND"}
|
||||||
|
)
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
auth_data[4]["user_id"],
|
||||||
|
f"Restored server {server_id} backup {data['filename']}",
|
||||||
|
server_id,
|
||||||
|
self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
123
app/classes/web/routes/api/servers/server/backups/index.py
Normal file
123
app/classes/web/routes/api/servers/server/backups/index.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from jsonschema import validate
|
||||||
|
from jsonschema.exceptions import ValidationError
|
||||||
|
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
backup_patch_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"backup_path": {"type": "string", "minLength": 1},
|
||||||
|
"max_backups": {"type": "integer"},
|
||||||
|
"compress": {"type": "boolean"},
|
||||||
|
"shutdown": {"type": "boolean"},
|
||||||
|
"backup_before": {"type": "string"},
|
||||||
|
"backup_after": {"type": "string"},
|
||||||
|
"exclusions": {"type": "array"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
basic_backup_patch_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"max_backups": {"type": "integer"},
|
||||||
|
"compress": {"type": "boolean"},
|
||||||
|
"shutdown": {"type": "boolean"},
|
||||||
|
"backup_before": {"type": "string"},
|
||||||
|
"backup_after": {"type": "string"},
|
||||||
|
"exclusions": {"type": "array"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ApiServersServerBackupsIndexHandler(BaseApiHandler):
|
||||||
|
def get(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
EnumPermissionsServer.BACKUP
|
||||||
|
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_backup_config(server_id))
|
||||||
|
|
||||||
|
def patch(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:
|
||||||
|
if auth_data[4]["superuser"]:
|
||||||
|
validate(data, backup_patch_schema)
|
||||||
|
else:
|
||||||
|
validate(data, basic_backup_patch_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
|
||||||
|
# if the user doesn't have access to the server, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
if (
|
||||||
|
EnumPermissionsServer.BACKUP
|
||||||
|
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.controller.management.set_backup_config(
|
||||||
|
server_id,
|
||||||
|
data.get(
|
||||||
|
"backup_path",
|
||||||
|
self.controller.management.get_backup_config(server_id)["backup_path"],
|
||||||
|
),
|
||||||
|
data.get(
|
||||||
|
"max_backups",
|
||||||
|
self.controller.management.get_backup_config(server_id)["max_backups"],
|
||||||
|
),
|
||||||
|
data.get("exclusions"),
|
||||||
|
data.get(
|
||||||
|
"compress",
|
||||||
|
self.controller.management.get_backup_config(server_id)["compress"],
|
||||||
|
),
|
||||||
|
data.get(
|
||||||
|
"shutdown",
|
||||||
|
self.controller.management.get_backup_config(server_id)["shutdown"],
|
||||||
|
),
|
||||||
|
data.get(
|
||||||
|
"backup_before",
|
||||||
|
self.controller.management.get_backup_config(server_id)["before"],
|
||||||
|
),
|
||||||
|
data.get(
|
||||||
|
"backup_after",
|
||||||
|
self.controller.management.get_backup_config(server_id)["after"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
555
app/classes/web/routes/api/servers/server/files.py
Normal file
555
app/classes/web/routes/api/servers/server/files.py
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import html
|
||||||
|
from jsonschema import validate
|
||||||
|
from jsonschema.exceptions import ValidationError
|
||||||
|
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||||
|
from app.classes.shared.helpers import Helpers
|
||||||
|
from app.classes.shared.file_helpers import FileHelpers
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
files_get_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"page": {"type": "string", "minLength": 1},
|
||||||
|
"path": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
files_patch_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": {"type": "string"},
|
||||||
|
"contents": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
files_unzip_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"folder": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
files_create_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"parent": {"type": "string"},
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"directory": {"type": "boolean"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
files_rename_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": {"type": "string"},
|
||||||
|
"new_name": {"type": "string"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
file_delete_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"filename": {"type": "string", "minLength": 5},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ApiServersServerFilesIndexHandler(BaseApiHandler):
|
||||||
|
def post(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.FILES
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
or EnumPermissionsServer.BACKUP
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files or Backup permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
|
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, files_get_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
data["path"],
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if os.path.isdir(data["path"]):
|
||||||
|
# TODO: limit some columns for specific permissions?
|
||||||
|
folder = data["path"]
|
||||||
|
return_json = {
|
||||||
|
"root_path": {
|
||||||
|
"path": folder,
|
||||||
|
"top": data["path"]
|
||||||
|
== self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir_list = []
|
||||||
|
unsorted_files = []
|
||||||
|
file_list = os.listdir(folder)
|
||||||
|
for item in file_list:
|
||||||
|
if os.path.isdir(os.path.join(folder, item)):
|
||||||
|
dir_list.append(item)
|
||||||
|
else:
|
||||||
|
unsorted_files.append(item)
|
||||||
|
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||||
|
unsorted_files, key=str.casefold
|
||||||
|
)
|
||||||
|
for raw_filename in file_list:
|
||||||
|
filename = html.escape(raw_filename)
|
||||||
|
rel = os.path.join(folder, raw_filename)
|
||||||
|
dpath = os.path.join(folder, filename)
|
||||||
|
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
|
||||||
|
server_id
|
||||||
|
):
|
||||||
|
if os.path.isdir(rel):
|
||||||
|
return_json[filename] = {
|
||||||
|
"path": dpath,
|
||||||
|
"dir": True,
|
||||||
|
"excluded": True,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return_json[filename] = {
|
||||||
|
"path": dpath,
|
||||||
|
"dir": False,
|
||||||
|
"excluded": True,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
if os.path.isdir(rel):
|
||||||
|
return_json[filename] = {
|
||||||
|
"path": dpath,
|
||||||
|
"dir": True,
|
||||||
|
"excluded": False,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return_json[filename] = {
|
||||||
|
"path": dpath,
|
||||||
|
"dir": False,
|
||||||
|
"excluded": False,
|
||||||
|
}
|
||||||
|
self.finish_json(200, {"status": "ok", "data": return_json})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open(data["path"], encoding="utf-8") as file:
|
||||||
|
file_contents = file.read()
|
||||||
|
except UnicodeDecodeError as ex:
|
||||||
|
self.finish_json(
|
||||||
|
400,
|
||||||
|
{"status": "error", "error": "DECODE_ERROR", "error_data": str(ex)},
|
||||||
|
)
|
||||||
|
self.finish_json(200, {"status": "ok", "data": file_contents})
|
||||||
|
|
||||||
|
def delete(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.FILES
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
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, file_delete_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
data["filename"],
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if os.path.isdir(data["filename"]):
|
||||||
|
FileHelpers.del_dirs(data["filename"])
|
||||||
|
else:
|
||||||
|
FileHelpers.del_file(data["filename"])
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
||||||
|
|
||||||
|
def patch(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.FILES
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
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, files_patch_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
data["path"],
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
file_path = Helpers.get_os_understandable_path(data["path"])
|
||||||
|
file_contents = data["contents"]
|
||||||
|
# Open the file in write mode and store the content in file_object
|
||||||
|
with open(file_path, "w", encoding="utf-8") as file_object:
|
||||||
|
file_object.write(file_contents)
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
||||||
|
|
||||||
|
def put(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.FILES
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
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, files_create_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
path = os.path.join(data["parent"], data["name"])
|
||||||
|
if not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
path,
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if Helpers.check_path_exists(os.path.abspath(path)):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "FILE EXISTS",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if data["directory"]:
|
||||||
|
os.mkdir(path)
|
||||||
|
else:
|
||||||
|
# Create the file by opening it
|
||||||
|
with open(path, "w", encoding="utf-8") as file_object:
|
||||||
|
file_object.close()
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
|
class ApiServersServerFilesCreateHandler(BaseApiHandler):
|
||||||
|
def patch(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.FILES
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
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, files_rename_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
path = data["path"]
|
||||||
|
new_item_name = data["new_name"]
|
||||||
|
new_item_path = os.path.join(os.path.split(path)[0], new_item_name)
|
||||||
|
if not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
path,
|
||||||
|
) or not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
new_item_path,
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if Helpers.check_path_exists(os.path.abspath(new_item_path)):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "FILE EXISTS",
|
||||||
|
"error_data": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
os.rename(path, new_item_path)
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
||||||
|
|
||||||
|
def put(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.FILES
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
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, files_create_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
path = os.path.join(data["parent"], data["name"])
|
||||||
|
if not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
path,
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if Helpers.check_path_exists(os.path.abspath(path)):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "FILE EXISTS",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if data["directory"]:
|
||||||
|
os.mkdir(path)
|
||||||
|
else:
|
||||||
|
# Create the file by opening it
|
||||||
|
with open(path, "w", encoding="utf-8") as file_object:
|
||||||
|
file_object.close()
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
|
class ApiServersServerFilesZipHandler(BaseApiHandler):
|
||||||
|
def post(self, server_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
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.FILES
|
||||||
|
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# if the user doesn't have Files permission, return an error
|
||||||
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
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, files_unzip_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
folder = data["folder"]
|
||||||
|
user_id = auth_data[4]["user_id"]
|
||||||
|
if not Helpers.validate_traversal(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||||
|
folder,
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "TRAVERSAL DETECTED",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if Helpers.check_file_exists(folder):
|
||||||
|
folder = self.file_helper.unzip_file(folder, user_id)
|
||||||
|
else:
|
||||||
|
if user_id:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "FILE_DOES_NOT_EXIST",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return self.finish_json(200, {"status": "ok"})
|
@ -74,6 +74,6 @@ class ApiServersServerLogsHandler(BaseApiHandler):
|
|||||||
|
|
||||||
if use_html:
|
if use_html:
|
||||||
for line in lines:
|
for line in lines:
|
||||||
self.write(f"{line}<br />")
|
line = f"{line}<br />"
|
||||||
else:
|
|
||||||
self.finish_json(200, {"status": "ok", "data": lines})
|
self.finish_json(200, {"status": "ok", "data": lines})
|
||||||
|
@ -93,10 +93,17 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
|||||||
"error_data": str(e),
|
"error_data": str(e),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
username = data["username"]
|
username = data["username"]
|
||||||
username = str(username).lower()
|
username = str(username).lower()
|
||||||
manager = int(user["user_id"])
|
manager = data.get("manager", None)
|
||||||
|
if user["superuser"]:
|
||||||
|
if (
|
||||||
|
manager == self.controller.users.get_id_by_name("SYSTEM")
|
||||||
|
or manager == 0
|
||||||
|
):
|
||||||
|
manager = None
|
||||||
|
else:
|
||||||
|
manager = int(user["user_id"])
|
||||||
password = data["password"]
|
password = data["password"]
|
||||||
email = data.get("email", "default@example.com")
|
email = data.get("email", "default@example.com")
|
||||||
enabled = data.get("enabled", True)
|
enabled = data.get("enabled", True)
|
||||||
|
243
app/classes/web/routes/api/users/user/api.py
Normal file
243
app/classes/web/routes/api/users/user/api.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from jsonschema import ValidationError, validate
|
||||||
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiUsersUserKeyHandler(BaseApiHandler):
|
||||||
|
def get(self, user_id: str, key_id=None):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
if key_id:
|
||||||
|
key = self.controller.users.get_user_api_key(key_id)
|
||||||
|
# does this user id exist?
|
||||||
|
if key is None:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID DATA",
|
||||||
|
"error_data": "INVALID KEY",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
str(key.user_id) != str(auth_data[4]["user_id"])
|
||||||
|
and not auth_data[4]["superuser"]
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NOT AUTHORIZED",
|
||||||
|
"error_data": "TRIED TO EDIT KEY WIHTOUT AUTH",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
auth_data[4]["user_id"],
|
||||||
|
f"Generated a new API token for the key {key.name} "
|
||||||
|
f"from user with UID: {key.user_id}",
|
||||||
|
server_id=0,
|
||||||
|
source_ip=self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
data_key = self.controller.authentication.generate(
|
||||||
|
key.user_id_id, {"token_id": key.token_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.finish_json(
|
||||||
|
200,
|
||||||
|
{"status": "ok", "data": data_key},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
str(user_id) != str(auth_data[4]["user_id"])
|
||||||
|
and not auth_data[4]["superuser"]
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NOT AUTHORIZED",
|
||||||
|
"error_data": "TRIED TO EDIT KEY WIHTOUT AUTH",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
keys = []
|
||||||
|
for key in self.controller.users.get_user_api_keys(str(user_id)):
|
||||||
|
keys.append(
|
||||||
|
{
|
||||||
|
"id": key.token_id,
|
||||||
|
"name": key.name,
|
||||||
|
"server_permissions": key.server_permissions,
|
||||||
|
"crafty_permissions": key.crafty_permissions,
|
||||||
|
"superuser": key.superuser,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": keys,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def patch(self, user_id: str):
|
||||||
|
user_key_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string", "minLength": 3},
|
||||||
|
"server_permissions_mask": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
|
||||||
|
},
|
||||||
|
"crafty_permissions_mask": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[01]{3}$", # 8 bits, see EnumPermissionsCrafty
|
||||||
|
},
|
||||||
|
"superuser": {"type": "boolean"},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"minProperties": 1,
|
||||||
|
}
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_exec_user_crafty_permissions,
|
||||||
|
_,
|
||||||
|
_superuser,
|
||||||
|
user,
|
||||||
|
) = auth_data
|
||||||
|
|
||||||
|
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, user_key_schema)
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_JSON_SCHEMA",
|
||||||
|
"error_data": str(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_id == "@me":
|
||||||
|
user_id = user["user_id"]
|
||||||
|
# does this user id exist?
|
||||||
|
if not self.controller.users.user_id_exists(user_id):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "USER NOT FOUND",
|
||||||
|
"error_data": "USER_NOT_FOUND",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
str(user_id) != str(auth_data[4]["user_id"])
|
||||||
|
and not auth_data[4]["superuser"]
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NOT AUTHORIZED",
|
||||||
|
"error_data": "TRIED TO EDIT KEY WIHTOUT AUTH",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
key_id = self.controller.users.add_user_api_key(
|
||||||
|
data["name"],
|
||||||
|
user_id,
|
||||||
|
data["superuser"],
|
||||||
|
data["server_permissions_mask"],
|
||||||
|
data["crafty_permissions_mask"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
auth_data[4]["user_id"],
|
||||||
|
f"Added API key {data['name']} with crafty permissions "
|
||||||
|
f"{data['crafty_permissions_mask']}"
|
||||||
|
f" and {data['server_permissions_mask']} for user with UID: {user_id}",
|
||||||
|
server_id=0,
|
||||||
|
source_ip=self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
self.finish_json(200, {"status": "ok", "data": {"id": key_id}})
|
||||||
|
|
||||||
|
def delete(self, _user_id: str, key_id: str):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
_exec_user_crafty_permissions,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_user,
|
||||||
|
) = auth_data
|
||||||
|
if key_id:
|
||||||
|
key = self.controller.users.get_user_api_key(key_id)
|
||||||
|
# does this user id exist?
|
||||||
|
if key is None:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID DATA",
|
||||||
|
"error_data": "INVALID KEY",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# does this user id exist?
|
||||||
|
target_key = self.controller.users.get_user_api_key(key_id)
|
||||||
|
if not target_key:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID KEY",
|
||||||
|
"error_data": "INVALID KEY ID",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
target_key.user_id != auth_data[4]["user_id"]
|
||||||
|
and not auth_data[4]["superuser"]
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NOT AUTHORIZED",
|
||||||
|
"error_data": "TRIED TO EDIT KEY WIHTOUT AUTH",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.controller.users.delete_user_api_key(key_id)
|
||||||
|
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
auth_data[4]["user_id"],
|
||||||
|
f"Removed API key {target_key} "
|
||||||
|
f"(ID: {key_id}) from user {auth_data[4]['user_id']}",
|
||||||
|
server_id=0,
|
||||||
|
source_ip=self.get_remote_ip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.finish_json(
|
||||||
|
200,
|
||||||
|
{"status": "ok", "data": {"id": key_id}},
|
||||||
|
)
|
@ -166,7 +166,13 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
|||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
400, {"status": "error", "error": "INVALID_USERNAME"}
|
400, {"status": "error", "error": "INVALID_USERNAME"}
|
||||||
)
|
)
|
||||||
if self.controller.users.get_id_by_name(data["username"]) is not None:
|
if self.controller.users.get_id_by_name(
|
||||||
|
data["username"]
|
||||||
|
) is not None and self.controller.users.get_id_by_name(
|
||||||
|
data["username"]
|
||||||
|
) != int(
|
||||||
|
user_id
|
||||||
|
):
|
||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
400, {"status": "error", "error": "USER_EXISTS"}
|
400, {"status": "error", "error": "USER_EXISTS"}
|
||||||
)
|
)
|
||||||
@ -210,13 +216,13 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
|||||||
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
|
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
|
||||||
)
|
)
|
||||||
|
|
||||||
if "password" in data and str(user["user_id"] == str(user_id)):
|
|
||||||
# TODO: edit your own password
|
|
||||||
return self.finish_json(
|
|
||||||
400, {"status": "error", "error": "INVALID_PASSWORD_MODIFY"}
|
|
||||||
)
|
|
||||||
|
|
||||||
user_obj = HelperUsers.get_user_model(user_id)
|
user_obj = HelperUsers.get_user_model(user_id)
|
||||||
|
if "password" in data and str(user["user_id"]) != str(user_id):
|
||||||
|
if str(user["user_id"]) != str(user_obj.manager):
|
||||||
|
# TODO: edit your own password
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "INVALID_PASSWORD_MODIFY"}
|
||||||
|
)
|
||||||
|
|
||||||
if "roles" in data:
|
if "roles" in data:
|
||||||
roles: t.Set[str] = set(data.pop("roles"))
|
roles: t.Set[str] = set(data.pop("roles"))
|
||||||
@ -236,6 +242,12 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
|||||||
user_id, removed_roles
|
user_id, removed_roles
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "manager" in data and (
|
||||||
|
data["manager"] == self.controller.users.get_id_by_name("SYSTEM")
|
||||||
|
or data["manager"] == 0
|
||||||
|
):
|
||||||
|
data["manager"] = None
|
||||||
|
|
||||||
if "permissions" in data:
|
if "permissions" in data:
|
||||||
permissions: t.List[UsersController.ApiPermissionDict] = data.pop(
|
permissions: t.List[UsersController.ApiPermissionDict] = data.pop(
|
||||||
"permissions"
|
"permissions"
|
||||||
@ -246,7 +258,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
|||||||
limit_role_creation = 0
|
limit_role_creation = 0
|
||||||
|
|
||||||
for permission in permissions:
|
for permission in permissions:
|
||||||
self.controller.crafty_perms.set_permission(
|
permissions_mask = self.controller.crafty_perms.set_permission(
|
||||||
permissions_mask,
|
permissions_mask,
|
||||||
EnumPermissionsCrafty.__members__[permission["name"]],
|
EnumPermissionsCrafty.__members__[permission["name"]],
|
||||||
"1" if permission["enabled"] else "0",
|
"1" if permission["enabled"] else "0",
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.escape
|
import tornado.escape
|
||||||
import bleach
|
|
||||||
|
|
||||||
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||||
from app.classes.shared.helpers import Helpers
|
from app.classes.shared.helpers import Helpers
|
||||||
from app.classes.shared.file_helpers import FileHelpers
|
|
||||||
from app.classes.shared.main_models import DatabaseShortcuts
|
from app.classes.shared.main_models import DatabaseShortcuts
|
||||||
from app.classes.web.base_handler import BaseHandler
|
from app.classes.web.base_handler import BaseHandler
|
||||||
|
|
||||||
@ -174,441 +170,3 @@ class ServerHandler(BaseHandler):
|
|||||||
data=page_data,
|
data=page_data,
|
||||||
translate=self.translator.translate,
|
translate=self.translator.translate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@tornado.web.authenticated
|
|
||||||
def post(self, page):
|
|
||||||
api_key, _token_data, exec_user = self.current_user
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
|
|
||||||
template = "public/404.html"
|
|
||||||
page_data = {
|
|
||||||
"version_data": "version_data_here", # TODO
|
|
||||||
"user_data": exec_user,
|
|
||||||
"show_contribute": self.helper.get_setting("show_contribute_link", True),
|
|
||||||
"background": self.controller.cached_login,
|
|
||||||
"lang": self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
|
|
||||||
"lang_page": Helpers.get_lang_page(
|
|
||||||
self.controller.users.get_user_lang_by_id(exec_user["user_id"])
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
if page == "command":
|
|
||||||
server_id = bleach.clean(self.get_argument("id", None))
|
|
||||||
command = bleach.clean(self.get_argument("command", None))
|
|
||||||
|
|
||||||
if server_id is not None:
|
|
||||||
if command == "clone_server":
|
|
||||||
if (
|
|
||||||
not superuser
|
|
||||||
and not self.controller.crafty_perms.can_create_server(
|
|
||||||
exec_user["user_id"]
|
|
||||||
)
|
|
||||||
):
|
|
||||||
time.sleep(3)
|
|
||||||
self.helper.websocket_helper.broadcast_user(
|
|
||||||
exec_user["user_id"],
|
|
||||||
"send_start_error",
|
|
||||||
{
|
|
||||||
"error": "<i class='fas fa-exclamation-triangle'"
|
|
||||||
" style='font-size:48px;color:red'>"
|
|
||||||
"</i> Not a server creator or server limit reached."
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
def is_name_used(name):
|
|
||||||
for server in self.controller.servers.get_all_defined_servers():
|
|
||||||
if server["server_name"] == name:
|
|
||||||
return True
|
|
||||||
return
|
|
||||||
|
|
||||||
template = "/panel/dashboard"
|
|
||||||
server_data = self.controller.servers.get_server_data_by_id(
|
|
||||||
server_id
|
|
||||||
)
|
|
||||||
new_server_name = server_data.get("server_name") + " (Copy)"
|
|
||||||
|
|
||||||
name_counter = 1
|
|
||||||
while is_name_used(new_server_name):
|
|
||||||
name_counter += 1
|
|
||||||
new_server_name = (
|
|
||||||
server_data.get("server_name") + f" (Copy {name_counter})"
|
|
||||||
)
|
|
||||||
|
|
||||||
new_server_uuid = Helpers.create_uuid()
|
|
||||||
while os.path.exists(
|
|
||||||
os.path.join(self.helper.servers_dir, new_server_uuid)
|
|
||||||
):
|
|
||||||
new_server_uuid = Helpers.create_uuid()
|
|
||||||
new_server_path = os.path.join(
|
|
||||||
self.helper.servers_dir, new_server_uuid
|
|
||||||
)
|
|
||||||
|
|
||||||
# copy the old server
|
|
||||||
FileHelpers.copy_dir(server_data.get("path"), new_server_path)
|
|
||||||
|
|
||||||
# TODO get old server DB data to individual variables
|
|
||||||
stop_command = server_data.get("stop_command")
|
|
||||||
new_server_command = str(server_data.get("execution_command"))
|
|
||||||
new_executable = server_data.get("executable")
|
|
||||||
new_server_log_file = str(
|
|
||||||
Helpers.get_os_understandable_path(server_data.get("log_path"))
|
|
||||||
)
|
|
||||||
backup_path = os.path.join(self.helper.backup_path, new_server_uuid)
|
|
||||||
server_port = server_data.get("server_port")
|
|
||||||
server_type = server_data.get("type")
|
|
||||||
created_by = exec_user["user_id"]
|
|
||||||
|
|
||||||
new_server_id = self.controller.servers.create_server(
|
|
||||||
new_server_name,
|
|
||||||
new_server_uuid,
|
|
||||||
new_server_path,
|
|
||||||
backup_path,
|
|
||||||
new_server_command,
|
|
||||||
new_executable,
|
|
||||||
new_server_log_file,
|
|
||||||
stop_command,
|
|
||||||
server_type,
|
|
||||||
created_by,
|
|
||||||
server_port,
|
|
||||||
)
|
|
||||||
if not exec_user["superuser"]:
|
|
||||||
new_server_uuid = self.controller.servers.get_server_data_by_id(
|
|
||||||
new_server_id
|
|
||||||
).get("server_uuid")
|
|
||||||
role_id = self.controller.roles.add_role(
|
|
||||||
f"Creator of Server with uuid={new_server_uuid}",
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
self.controller.server_perms.add_role_server(
|
|
||||||
new_server_id, role_id, "11111111"
|
|
||||||
)
|
|
||||||
self.controller.users.add_role_to_user(
|
|
||||||
exec_user["user_id"], role_id
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.servers.init_all_servers()
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
self.controller.management.send_command(
|
|
||||||
exec_user["user_id"], server_id, self.get_remote_ip(), command
|
|
||||||
)
|
|
||||||
|
|
||||||
if page == "step1":
|
|
||||||
if not superuser and not self.controller.crafty_perms.can_create_server(
|
|
||||||
exec_user["user_id"]
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: "
|
|
||||||
"not a server creator or server limit reached"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not superuser:
|
|
||||||
user_roles = self.controller.roles.get_all_roles()
|
|
||||||
else:
|
|
||||||
user_roles = self.get_user_roles()
|
|
||||||
server = bleach.clean(self.get_argument("server", ""))
|
|
||||||
server_name = bleach.clean(self.get_argument("server_name", ""))
|
|
||||||
min_mem = bleach.clean(self.get_argument("min_memory", ""))
|
|
||||||
max_mem = bleach.clean(self.get_argument("max_memory", ""))
|
|
||||||
port = bleach.clean(self.get_argument("port", ""))
|
|
||||||
if int(port) < 1 or int(port) > 65535:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Constraint Error: "
|
|
||||||
"Port must be greater than 0 and less than 65535"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
import_type = bleach.clean(self.get_argument("create_type", ""))
|
|
||||||
import_server_path = bleach.clean(self.get_argument("server_path", ""))
|
|
||||||
import_server_jar = bleach.clean(self.get_argument("server_jar", ""))
|
|
||||||
server_parts = server.split("|")
|
|
||||||
captured_roles = []
|
|
||||||
for role in user_roles:
|
|
||||||
if bleach.clean(self.get_argument(str(role), "")) == "on":
|
|
||||||
captured_roles.append(role)
|
|
||||||
|
|
||||||
if not server_name:
|
|
||||||
self.redirect("/panel/error?error=Server name cannot be empty!")
|
|
||||||
return
|
|
||||||
|
|
||||||
if import_type == "import_jar":
|
|
||||||
if self.helper.is_subdir(
|
|
||||||
self.controller.project_root, import_server_path
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Loop Error: The selected path will cause"
|
|
||||||
" an infinite copy loop. Make sure Crafty's directory is not"
|
|
||||||
" in your server path."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
good_path = self.controller.verify_jar_server(
|
|
||||||
import_server_path, import_server_jar
|
|
||||||
)
|
|
||||||
|
|
||||||
if not good_path:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Server path or Server Jar not found!"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
new_server_id = self.controller.import_jar_server(
|
|
||||||
server_name,
|
|
||||||
import_server_path,
|
|
||||||
import_server_jar,
|
|
||||||
min_mem,
|
|
||||||
max_mem,
|
|
||||||
port,
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f'imported a jar server named "{server_name}"',
|
|
||||||
new_server_id,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
elif import_type == "import_zip":
|
|
||||||
# here import_server_path means the zip path
|
|
||||||
zip_path = bleach.clean(self.get_argument("root_path"))
|
|
||||||
good_path = Helpers.check_path_exists(zip_path)
|
|
||||||
if not good_path:
|
|
||||||
self.redirect("/panel/error?error=Temp path not found!")
|
|
||||||
return
|
|
||||||
|
|
||||||
new_server_id = self.controller.import_zip_server(
|
|
||||||
server_name,
|
|
||||||
zip_path,
|
|
||||||
import_server_jar,
|
|
||||||
min_mem,
|
|
||||||
max_mem,
|
|
||||||
port,
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
if new_server_id == "false":
|
|
||||||
self.redirect(
|
|
||||||
f"/panel/error?error=Zip file not accessible! "
|
|
||||||
f"You can fix this permissions issue with "
|
|
||||||
f"sudo chown -R crafty:crafty {import_server_path} "
|
|
||||||
f"And sudo chmod 2775 -R {import_server_path}"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f'imported a zip server named "{server_name}"',
|
|
||||||
new_server_id,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if len(server_parts) != 3:
|
|
||||||
self.redirect("/panel/error?error=Invalid server data")
|
|
||||||
return
|
|
||||||
jar_type, server_type, server_version = server_parts
|
|
||||||
# TODO: add server type check here and call the correct server
|
|
||||||
# add functions if not a jar
|
|
||||||
if server_type == "forge" and not self.helper.detect_java():
|
|
||||||
translation = self.helper.translation.translate(
|
|
||||||
"error",
|
|
||||||
"installerJava",
|
|
||||||
self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
|
|
||||||
).format(server_name)
|
|
||||||
self.redirect(f"/panel/error?error={translation}")
|
|
||||||
return
|
|
||||||
new_server_id = self.controller.create_jar_server(
|
|
||||||
jar_type,
|
|
||||||
server_type,
|
|
||||||
server_version,
|
|
||||||
server_name,
|
|
||||||
min_mem,
|
|
||||||
max_mem,
|
|
||||||
port,
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f"created a {server_version} {str(server_type).capitalize()}"
|
|
||||||
f' server named "{server_name}"',
|
|
||||||
# Example: Admin created a 1.16.5 Bukkit server named "survival"
|
|
||||||
new_server_id,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# These lines create a new Role for the Server with full permissions
|
|
||||||
# and add the user to it if he's not a superuser
|
|
||||||
if len(captured_roles) == 0:
|
|
||||||
if not superuser:
|
|
||||||
new_server_uuid = self.controller.servers.get_server_data_by_id(
|
|
||||||
new_server_id
|
|
||||||
).get("server_uuid")
|
|
||||||
role_id = self.controller.roles.add_role(
|
|
||||||
f"Creator of Server with uuid={new_server_uuid}",
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
self.controller.server_perms.add_role_server(
|
|
||||||
new_server_id, role_id, "11111111"
|
|
||||||
)
|
|
||||||
self.controller.users.add_role_to_user(
|
|
||||||
exec_user["user_id"], role_id
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
for role in captured_roles:
|
|
||||||
role_id = role
|
|
||||||
self.controller.server_perms.add_role_server(
|
|
||||||
new_server_id, role_id, "11111111"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.servers.stats.record_stats()
|
|
||||||
self.redirect("/panel/dashboard")
|
|
||||||
|
|
||||||
if page == "bedrock_step1":
|
|
||||||
if not superuser and not self.controller.crafty_perms.can_create_server(
|
|
||||||
exec_user["user_id"]
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Unauthorized access: "
|
|
||||||
"not a server creator or server limit reached"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if not superuser:
|
|
||||||
user_roles = self.controller.roles.get_all_roles()
|
|
||||||
else:
|
|
||||||
user_roles = self.controller.roles.get_all_roles()
|
|
||||||
server = bleach.clean(self.get_argument("server", ""))
|
|
||||||
server_name = bleach.clean(self.get_argument("server_name", ""))
|
|
||||||
port = bleach.clean(self.get_argument("port", ""))
|
|
||||||
|
|
||||||
if not port:
|
|
||||||
port = 19132
|
|
||||||
if int(port) < 1 or int(port) > 65535:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Constraint Error: "
|
|
||||||
"Port must be greater than 0 and less than 65535"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
import_type = bleach.clean(self.get_argument("create_type", ""))
|
|
||||||
import_server_path = bleach.clean(self.get_argument("server_path", ""))
|
|
||||||
import_server_exe = bleach.clean(self.get_argument("server_jar", ""))
|
|
||||||
server_parts = server.split("|")
|
|
||||||
captured_roles = []
|
|
||||||
for role in user_roles:
|
|
||||||
if bleach.clean(self.get_argument(str(role), "")) == "on":
|
|
||||||
captured_roles.append(role)
|
|
||||||
|
|
||||||
if not server_name:
|
|
||||||
self.redirect("/panel/error?error=Server name cannot be empty!")
|
|
||||||
return
|
|
||||||
|
|
||||||
if import_type == "import_jar":
|
|
||||||
if self.helper.is_subdir(
|
|
||||||
self.controller.project_root, import_server_path
|
|
||||||
):
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Loop Error: The selected path will cause"
|
|
||||||
" an infinite copy loop. Make sure Crafty's directory is not"
|
|
||||||
" in your server path."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
good_path = self.controller.verify_jar_server(
|
|
||||||
import_server_path, import_server_exe
|
|
||||||
)
|
|
||||||
|
|
||||||
if not good_path:
|
|
||||||
self.redirect(
|
|
||||||
"/panel/error?error=Server path or Server Jar not found!"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
new_server_id = self.controller.import_bedrock_server(
|
|
||||||
server_name,
|
|
||||||
import_server_path,
|
|
||||||
import_server_exe,
|
|
||||||
port,
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f'imported a jar server named "{server_name}"',
|
|
||||||
new_server_id,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
elif import_type == "import_zip":
|
|
||||||
# here import_server_path means the zip path
|
|
||||||
zip_path = bleach.clean(self.get_argument("root_path"))
|
|
||||||
good_path = Helpers.check_path_exists(zip_path)
|
|
||||||
if not good_path:
|
|
||||||
self.redirect("/panel/error?error=Temp path not found!")
|
|
||||||
return
|
|
||||||
|
|
||||||
new_server_id = self.controller.import_bedrock_zip_server(
|
|
||||||
server_name,
|
|
||||||
zip_path,
|
|
||||||
import_server_exe,
|
|
||||||
port,
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
if new_server_id == "false":
|
|
||||||
self.redirect(
|
|
||||||
f"/panel/error?error=Zip file not accessible! "
|
|
||||||
f"You can fix this permissions issue with"
|
|
||||||
f"sudo chown -R crafty:crafty {import_server_path} "
|
|
||||||
f"And sudo chmod 2775 -R {import_server_path}"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
f'imported a zip server named "{server_name}"',
|
|
||||||
new_server_id,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
new_server_id = self.controller.create_bedrock_server(
|
|
||||||
server_name,
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
self.controller.management.add_to_audit_log(
|
|
||||||
exec_user["user_id"],
|
|
||||||
"created a Bedrock " f'server named "{server_name}"',
|
|
||||||
# Example: Admin created a 1.16.5 Bukkit server named "survival"
|
|
||||||
new_server_id,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# These lines create a new Role for the Server with full permissions
|
|
||||||
# and add the user to it if he's not a superuser
|
|
||||||
if len(captured_roles) == 0:
|
|
||||||
if not superuser:
|
|
||||||
new_server_uuid = self.controller.servers.get_server_data_by_id(
|
|
||||||
new_server_id
|
|
||||||
).get("server_uuid")
|
|
||||||
role_id = self.controller.roles.add_role(
|
|
||||||
f"Creator of Server with uuid={new_server_uuid}",
|
|
||||||
exec_user["user_id"],
|
|
||||||
)
|
|
||||||
self.controller.server_perms.add_role_server(
|
|
||||||
new_server_id, role_id, "11111111"
|
|
||||||
)
|
|
||||||
self.controller.users.add_role_to_user(
|
|
||||||
exec_user["user_id"], role_id
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
for role in captured_roles:
|
|
||||||
role_id = role
|
|
||||||
self.controller.server_perms.add_role_server(
|
|
||||||
new_server_id, role_id, "11111111"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.controller.servers.stats.record_stats()
|
|
||||||
self.redirect("/panel/dashboard")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.render(
|
|
||||||
template,
|
|
||||||
data=page_data,
|
|
||||||
translate=self.translator.translate,
|
|
||||||
)
|
|
||||||
except RuntimeError:
|
|
||||||
self.redirect("/panel/dashboard")
|
|
||||||
|
@ -15,13 +15,11 @@ from app.classes.models.management import HelpersManagement
|
|||||||
from app.classes.shared.console import Console
|
from app.classes.shared.console import Console
|
||||||
from app.classes.shared.helpers import Helpers
|
from app.classes.shared.helpers import Helpers
|
||||||
from app.classes.shared.main_controller import Controller
|
from app.classes.shared.main_controller import Controller
|
||||||
from app.classes.web.file_handler import FileHandler
|
|
||||||
from app.classes.web.public_handler import PublicHandler
|
from app.classes.web.public_handler import PublicHandler
|
||||||
from app.classes.web.panel_handler import PanelHandler
|
from app.classes.web.panel_handler import PanelHandler
|
||||||
from app.classes.web.default_handler import DefaultHandler
|
from app.classes.web.default_handler import DefaultHandler
|
||||||
from app.classes.web.routes.api.api_handlers import api_handlers
|
from app.classes.web.routes.api.api_handlers import api_handlers
|
||||||
from app.classes.web.server_handler import ServerHandler
|
from app.classes.web.server_handler import ServerHandler
|
||||||
from app.classes.web.ajax_handler import AjaxHandler
|
|
||||||
from app.classes.web.api_handler import (
|
from app.classes.web.api_handler import (
|
||||||
ServersStats,
|
ServersStats,
|
||||||
NodeStats,
|
NodeStats,
|
||||||
@ -48,13 +46,14 @@ class Webserver:
|
|||||||
controller: Controller
|
controller: Controller
|
||||||
helper: Helpers
|
helper: Helpers
|
||||||
|
|
||||||
def __init__(self, helper, controller, tasks_manager):
|
def __init__(self, helper, controller, tasks_manager, file_helper):
|
||||||
self.ioloop = None
|
self.ioloop = None
|
||||||
self.http_server = None
|
self.http_server = None
|
||||||
self.https_server = None
|
self.https_server = None
|
||||||
self.helper = helper
|
self.helper = helper
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.tasks_manager = tasks_manager
|
self.tasks_manager = tasks_manager
|
||||||
|
self.file_helper = file_helper
|
||||||
self._asyncio_patch()
|
self._asyncio_patch()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -146,13 +145,12 @@ class Webserver:
|
|||||||
"controller": self.controller,
|
"controller": self.controller,
|
||||||
"tasks_manager": self.tasks_manager,
|
"tasks_manager": self.tasks_manager,
|
||||||
"translator": self.helper.translation,
|
"translator": self.helper.translation,
|
||||||
|
"file_helper": self.file_helper,
|
||||||
}
|
}
|
||||||
handlers = [
|
handlers = [
|
||||||
(r"/", DefaultHandler, handler_args),
|
(r"/", DefaultHandler, handler_args),
|
||||||
(r"/panel/(.*)", PanelHandler, handler_args),
|
(r"/panel/(.*)", PanelHandler, handler_args),
|
||||||
(r"/server/(.*)", ServerHandler, handler_args),
|
(r"/server/(.*)", ServerHandler, handler_args),
|
||||||
(r"/ajax/(.*)", AjaxHandler, handler_args),
|
|
||||||
(r"/files/(.*)", FileHandler, handler_args),
|
|
||||||
(r"/ws", SocketHandler, handler_args),
|
(r"/ws", SocketHandler, handler_args),
|
||||||
(r"/upload", UploadHandler, handler_args),
|
(r"/upload", UploadHandler, handler_args),
|
||||||
(r"/status", StatusHandler, handler_args),
|
(r"/status", StatusHandler, handler_args),
|
||||||
|
@ -25,11 +25,13 @@ class UploadHandler(BaseHandler):
|
|||||||
controller: Controller = None,
|
controller: Controller = None,
|
||||||
tasks_manager=None,
|
tasks_manager=None,
|
||||||
translator=None,
|
translator=None,
|
||||||
|
file_helper=None,
|
||||||
):
|
):
|
||||||
self.helper = helper
|
self.helper = helper
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.tasks_manager = tasks_manager
|
self.tasks_manager = tasks_manager
|
||||||
self.translator = translator
|
self.translator = translator
|
||||||
|
self.file_helper = file_helper
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
# Class & Function Defination
|
# Class & Function Defination
|
||||||
|
@ -18,12 +18,18 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
|||||||
io_loop = None
|
io_loop = None
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
self, helper=None, controller=None, tasks_manager=None, translator=None
|
self,
|
||||||
|
helper=None,
|
||||||
|
controller=None,
|
||||||
|
tasks_manager=None,
|
||||||
|
translator=None,
|
||||||
|
file_helper=None,
|
||||||
):
|
):
|
||||||
self.helper = helper
|
self.helper = helper
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.tasks_manager = tasks_manager
|
self.tasks_manager = tasks_manager
|
||||||
self.translator = translator
|
self.translator = translator
|
||||||
|
self.file_helper = file_helper
|
||||||
self.io_loop = tornado.ioloop.IOLoop.current()
|
self.io_loop = tornado.ioloop.IOLoop.current()
|
||||||
|
|
||||||
def get_remote_ip(self):
|
def get_remote_ip(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"major": 4,
|
"major": 4,
|
||||||
"minor": 1,
|
"minor": 2,
|
||||||
"sub": 4
|
"sub": 0
|
||||||
}
|
}
|
||||||
|
137
app/frontend/static/assets/js/shared/root-dir.js
Normal file
137
app/frontend/static/assets/js/shared/root-dir.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
|
||||||
|
function show_file_tree() {
|
||||||
|
$("#dir_select").modal();
|
||||||
|
}
|
||||||
|
function getDirView(event = false) {
|
||||||
|
if (event) {
|
||||||
|
try {
|
||||||
|
let path = event.target.parentElement.getAttribute('data-path');
|
||||||
|
if (event.target.parentElement.classList.contains('clicked')) {
|
||||||
|
|
||||||
|
if ($(`#${path}span`).hasClass('files-tree-title')) {
|
||||||
|
$(`#${path}ul`).toggleClass("d-block");
|
||||||
|
$(`#${path}span`).toggleClass("tree-caret-down");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
getTreeView(path);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.log("Well that failed");
|
||||||
|
}
|
||||||
|
} else if ($("#root_files_button").hasClass("clicked")) {
|
||||||
|
getTreeView($("#zip_server_path").val(), true);
|
||||||
|
} else {
|
||||||
|
getTreeView($("#file-uploaded").val(), true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getTreeView(path, unzip = false, upload = false) {
|
||||||
|
const token = getCookie("_xsrf");
|
||||||
|
console.log("IN TREE VIEW")
|
||||||
|
console.log({ "page": "import", "folder": path, "upload": upload, "unzip": unzip });
|
||||||
|
let res = await fetch(`/api/v2/import/file/unzip/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ "page": "import", "folder": path, "upload": upload, "unzip": unzip }),
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
console.log(responseData);
|
||||||
|
process_tree_response(responseData);
|
||||||
|
let x = document.querySelector('.bootbox');
|
||||||
|
if (x) {
|
||||||
|
x.remove()
|
||||||
|
}
|
||||||
|
x = document.querySelector('.modal-backdrop');
|
||||||
|
if (x) {
|
||||||
|
x.remove()
|
||||||
|
}
|
||||||
|
show_file_tree();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function process_tree_response(response) {
|
||||||
|
const styles = window.getComputedStyle(document.getElementById("lower_half"));
|
||||||
|
//If this value is still hidden we know the user is executing a zip import and not an upload
|
||||||
|
if (styles.visibility === "hidden") {
|
||||||
|
document.getElementById('zip_submit').disabled = false;
|
||||||
|
} else {
|
||||||
|
document.getElementById('upload_submit').disabled = false;
|
||||||
|
}
|
||||||
|
let path = response.data.root_path.path;
|
||||||
|
$(".root-input").val(response.data.root_path.path);
|
||||||
|
let text = `<ul class="tree-nested d-block" id="${path}ul">`;
|
||||||
|
Object.entries(response.data).forEach(([key, value]) => {
|
||||||
|
if (key === "root_path" || key === "db_stats") {
|
||||||
|
//continue is not valid in for each. Return acts as a continue.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dpath = value.path;
|
||||||
|
let filename = key;
|
||||||
|
if (value.dir) {
|
||||||
|
text += `<li class="tree-item" id="${dpath}li" data-path="${dpath}">
|
||||||
|
<div id="${dpath}" data-path="${dpath}" data-name="${filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||||
|
<input type="radio" name="root_path" value="${dpath}">
|
||||||
|
<span id="${dpath}span" class="files-tree-title" data-path="${dpath}" data-name="${filename}" onclick="getDirView(event)">
|
||||||
|
<i style="color: var(--info);" class="far fa-folder"></i>
|
||||||
|
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
||||||
|
${filename}
|
||||||
|
</span>
|
||||||
|
</input></div><li>`
|
||||||
|
} else {
|
||||||
|
text += `<li
|
||||||
|
class="d-block tree-ctx-item tree-file"
|
||||||
|
data-path="${dpath}"
|
||||||
|
data-name="${filename}"
|
||||||
|
onclick="" id="${dpath}li"><input type='radio' class="checkBoxClass d-none file-check" name='root_path' value="${dpath}" disabled><span style="margin-right: 6px;">
|
||||||
|
<i class="far fa-file"></i></span></input>${filename}</li>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
text += `</ul>`;
|
||||||
|
|
||||||
|
if (response.data.root_path.top) {
|
||||||
|
try {
|
||||||
|
document.getElementById('main-tree-div').innerHTML += text;
|
||||||
|
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||||
|
} catch {
|
||||||
|
document.getElementById('files-tree').innerHTML = text;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
document.getElementById(path + "span").classList.add('tree-caret-down');
|
||||||
|
document.getElementById(path).innerHTML += text;
|
||||||
|
document.getElementById(path).classList.add("clicked");
|
||||||
|
} catch {
|
||||||
|
console.log("Bad")
|
||||||
|
}
|
||||||
|
|
||||||
|
let toggler = document.getElementById(path + "span");
|
||||||
|
|
||||||
|
if (toggler.classList.contains('files-tree-title')) {
|
||||||
|
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
||||||
|
document.getElementById(path + "ul").classList.toggle("d-block");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToggleMain(event) {
|
||||||
|
const path = event.target.parentElement.getAttribute('data-path');
|
||||||
|
document.getElementById("files-tree").classList.toggle("d-block");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret");
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
// This is the "Offline page" service worker
|
// This is the "Offline page" service worker
|
||||||
|
|
||||||
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
|
importScripts(
|
||||||
|
"https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js"
|
||||||
|
);
|
||||||
|
|
||||||
const CACHE = "crafty-controller";
|
const CACHE = "crafty-controller";
|
||||||
|
|
||||||
@ -8,39 +10,32 @@ const CACHE = "crafty-controller";
|
|||||||
const offlineFallbackPage = "/offline";
|
const offlineFallbackPage = "/offline";
|
||||||
|
|
||||||
self.addEventListener("message", (event) => {
|
self.addEventListener("message", (event) => {
|
||||||
if (event.data && event.data.type === "SKIP_WAITING") {
|
if (event.data && event.data.type === "SKIP_WAITING") {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('install', async (event) => {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.open(CACHE)
|
|
||||||
.then((cache) => cache.add(offlineFallbackPage))
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (workbox.navigationPreload.isSupported()) {
|
if (workbox.navigationPreload.isSupported()) {
|
||||||
workbox.navigationPreload.enable();
|
workbox.navigationPreload.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
// self.addEventListener('fetch', (event) => {
|
||||||
if (event.request.mode === 'navigate') {
|
// if (event.request.mode === 'navigate') {
|
||||||
event.respondWith((async () => {
|
// event.respondWith((async () => {
|
||||||
try {
|
// try {
|
||||||
const preloadResp = await event.preloadResponse;
|
// const preloadResp = await event.preloadResponse;
|
||||||
|
|
||||||
if (preloadResp) {
|
// if (preloadResp) {
|
||||||
return preloadResp;
|
// return preloadResp;
|
||||||
}
|
// }
|
||||||
const networkResp = await fetch(event.request);
|
// const networkResp = await fetch(event.request);
|
||||||
return networkResp;
|
// return networkResp;
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
|
|
||||||
const cache = await caches.open(CACHE);
|
// const cache = await caches.open(CACHE);
|
||||||
const cachedResp = await cache.match(offlineFallbackPage);
|
// const cachedResp = await cache.match(offlineFallbackPage);
|
||||||
return cachedResp;
|
// return cachedResp;
|
||||||
}
|
// }
|
||||||
})());
|
// })());
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
{% for item in data['notify_data'] %}
|
|
||||||
<!-- <div class="hidden">{{ item['id'] }}</div>-->
|
|
||||||
<div class="event">
|
|
||||||
<p class="font-weight-medium">{{ item['title'] }}</p>
|
|
||||||
<a class="d-flex align-items-center">
|
|
||||||
<div class="badge badge-primary">{{ item['date'] }}</div>
|
|
||||||
<span class="text-muted ml-2">{{ item['desc'] }}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% end %}
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
|||||||
href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" />
|
href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" />
|
||||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
||||||
<link rel="stylesheet" href="/static/assets/css/crafty.css">
|
<link rel="stylesheet" href="/static/assets/css/crafty.css">
|
||||||
|
|
||||||
<link rel="manifest" href="/static/assets/crafty.webmanifest">
|
<link rel="manifest" href="/static/assets/crafty.webmanifest">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
@ -412,20 +412,26 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function eulaAgree(server_id, command) {
|
async function eulaAgree(server_id, command) {
|
||||||
//< !--this getCookie function is in base.html-- >
|
//< !--this getCookie function is in base.html-- >
|
||||||
var token = getCookie("_xsrf");
|
const token = getCookie("_xsrf");
|
||||||
|
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/servers/${server_id}/action/eula/`, {
|
||||||
type: "POST",
|
method: 'POST',
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: {
|
||||||
url: '/ajax/eula?id=' + server_id,
|
'X-XSRFToken': token
|
||||||
success: function (data) {
|
},
|
||||||
console.log("got response:");
|
|
||||||
console.log(data);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -538,7 +544,7 @@
|
|||||||
});
|
});
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
|
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', { scope: '/' })
|
||||||
.then(function (registration) {
|
.then(function (registration) {
|
||||||
console.log('Service Worker Registered');
|
console.log('Service Worker Registered');
|
||||||
});
|
});
|
||||||
|
@ -1,27 +1,32 @@
|
|||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link count-indicator">
|
<a class="nav-link count-indicator dropdown-toggle" id="notifDropdown" href="#" data-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
<i class="fas fa-broadcast-tower
|
<i class="fas fa-broadcast-tower
|
||||||
{% if data.get('update_available') %}
|
{% if data.get('update_available') %}
|
||||||
text-danger
|
text-danger
|
||||||
{% end %}
|
{% end %}
|
||||||
"></i>
|
"></i><span id="notif-count" class="badge badge-notify"></span> </a>
|
||||||
<!-- <span class="count bg-success">3</span>-->
|
<div class="dropdown-menu dropdown-menu-right navbar-dropdown notif-div" style="width: 40vw; max-height: 80vh;" aria-labelledby="notifDropdown">
|
||||||
</a>
|
<ul style="padding-top: 10px;" id="announcements">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link count-indicator" href="/panel/panel_config">
|
<a class="nav-link" href="/panel/panel_config">
|
||||||
<i class="fas fa-cogs"></i>
|
<i class="fas fa-cogs"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item dropdown user-dropdown">
|
<li class="nav-item dropdown user-dropdown">
|
||||||
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
|
||||||
<img class="img-xs rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image"> </a>
|
<img class="img-xs rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}"
|
||||||
|
alt="Profile image"> </a>
|
||||||
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
|
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
|
||||||
<div class="dropdown-header text-center">
|
<div class="dropdown-header text-center">
|
||||||
<img class="img-md rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image">
|
<img class="img-md rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}"
|
||||||
|
alt="Profile image">
|
||||||
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
|
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
|
||||||
<p class="font-weight-light text-muted mb-0">Roles: </p>
|
<p class="font-weight-light text-muted mb-0">Roles: </p>
|
||||||
{% for r in data['user_role'] %}
|
{% for r in data['user_role'] %}
|
||||||
@ -33,27 +38,130 @@
|
|||||||
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
|
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% if data['user_data']['preparing'] %}
|
{% if data['user_data']['preparing'] %}
|
||||||
<span class="dropdown-item" id="support_progress"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}<br><br></span>
|
<span class="dropdown-item" id="support_progress"><i
|
||||||
|
class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs',
|
||||||
|
data['lang']) }}<br><br></span>
|
||||||
<span class="dropdown-item" id="support_progress">
|
<span class="dropdown-item" id="support_progress">
|
||||||
<div class="support_progress" style="height: 15px; width: 100%;">
|
<div class="support_progress" style="height: 15px; width: 100%;">
|
||||||
<div class="progress-bar progress-bar-striped progress-bar-animated" id="logs_progress_bar" role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
|
<div class="progress-bar progress-bar-striped progress-bar-animated" id="logs_progress_bar" role="progressbar"
|
||||||
|
style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="dropdown-item" id="support_logs"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}</i></a>
|
<a class="dropdown-item" id="support_logs"><i
|
||||||
|
class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs',
|
||||||
|
data['lang']) }}</i></a>
|
||||||
{% end %}
|
{% end %}
|
||||||
{% if data['superuser'] %}
|
{% if data['superuser'] %}
|
||||||
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i>{{ translate('notify', 'activityLog', data['lang']) }}</a>
|
<a class="dropdown-item" href="/panel/activity_logs"><i
|
||||||
|
class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i>{{ translate('notify',
|
||||||
|
'activityLog', data['lang']) }}</a>
|
||||||
{% end %}
|
{% end %}
|
||||||
<a class="dropdown-item" href="/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{ translate('notify', 'logout', data['lang']) }}</a>
|
<a class="dropdown-item" href="/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{
|
||||||
|
translate('notify', 'logout', data['lang']) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<style>
|
||||||
|
.badge-notify {
|
||||||
|
background: var(--purple);
|
||||||
|
position: absolute;
|
||||||
|
-moz-transform: translate(-70%, -70%);
|
||||||
|
/* For Firefox */
|
||||||
|
-ms-transform: translate(-70%, -70%);
|
||||||
|
/* for IE */
|
||||||
|
-webkit-transform: translate(-70%, -70%);
|
||||||
|
/* For Safari, Chrome, iOS */
|
||||||
|
-o-transform: translate(-70%, -70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
|
.notif-div::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
|
.notif-div {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
function pfpError(image) {
|
function pfpError(image) {
|
||||||
image.onerror = "";
|
image.onerror = "";
|
||||||
image.src = "/static/assets/images/faces-clipart/pic-3.png";
|
image.src = "/static/assets/images/faces-clipart/pic-3.png";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
function updateAnnouncements(data) {
|
||||||
|
console.log(data)
|
||||||
|
let text = "";
|
||||||
|
for (let value of data) {
|
||||||
|
text += `<li class="card-header header-sm justify-content-between align-items-center" id="${value.id}"><p style="float: right;"><i data-id="${value.id}"class="clear-button fa-regular fa-x"></i></p><a style="color: var(--purple);" href=${value.link} target="_blank"><h6>${value.title}</h6><small><p>${value.date}</p></small><p>${value.desc}</p></li></a>`
|
||||||
|
}
|
||||||
|
if (data.length > 0) {
|
||||||
|
localStorage.setItem("notif-count", data.length);
|
||||||
|
$("#notif-count").html(data.length);
|
||||||
|
$("#announcements").html(text);
|
||||||
|
} else {
|
||||||
|
$("#announcements").html(`<p style='margin-top: 15px;' class='text-center'><i class="fa fa-bell-slash" aria-hidden="true"></i>
|
||||||
|
</p>`);
|
||||||
|
}
|
||||||
|
$(".clear-button").on("click", function (event) {
|
||||||
|
console.log("CLEAR BUTTON")
|
||||||
|
let uuid = event.target.getAttribute("data-id");
|
||||||
|
$(`#${uuid}`).remove();
|
||||||
|
send_clear(uuid);
|
||||||
|
let notif_count = localStorage.getItem("notif-count") - 1;
|
||||||
|
if (notif_count > 0) {
|
||||||
|
localStorage.setItem("notif-count", notif_count);
|
||||||
|
$("#notif-count").html(notif_count);
|
||||||
|
} else {
|
||||||
|
$("#announcements").html(`<p style='margin-top: 15px;' class='text-center'><i class="fa fa-bell-slash" aria-hidden="true"></i>
|
||||||
|
</p>`)
|
||||||
|
$("#notif-count").html("");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function getAnnouncements() {
|
||||||
|
var token = getCookie("_xsrf");
|
||||||
|
let res = await fetch(`/api/v2/crafty/announcements/`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
console.log(responseData);
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
updateAnnouncements(responseData.data)
|
||||||
|
} else {
|
||||||
|
updateAnnouncements("<li><p>Trouble Getting Annoucements</p></li>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function send_clear(uuid) {
|
||||||
|
var token = getCookie("_xsrf");
|
||||||
|
let body = JSON.stringify({ "id": uuid });
|
||||||
|
console.log(body)
|
||||||
|
let res = await fetch(`/api/v2/crafty/announcements/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token,
|
||||||
|
},
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
console.log(responseData);
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
bootbox.alert(responseData.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(document).ready(function () {
|
||||||
|
getAnnouncements();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
@ -6,7 +6,8 @@
|
|||||||
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
|
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
|
||||||
|
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
@ -50,7 +51,6 @@
|
|||||||
<!-- Page Title Header Ends-->
|
<!-- Page Title Header Ends-->
|
||||||
|
|
||||||
<form id="config-form" class="forms-sample" method="post" action="/panel/config_json">
|
<form id="config-form" class="forms-sample" method="post" action="/panel/config_json">
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
|
|
||||||
{% for item in data['config-json'].items() %}
|
{% for item in data['config-json'].items() %}
|
||||||
{% if item[0] == "reset_secrets_on_next_boot" %}
|
{% if item[0] == "reset_secrets_on_next_boot" %}
|
||||||
@ -73,8 +73,11 @@
|
|||||||
</select>
|
</select>
|
||||||
{% elif item[0] == 'disabled_language_files' %}
|
{% elif item[0] == 'disabled_language_files' %}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{ translate('panelConfig', 'enableLang', data['lang']) }}</button>
|
<button type="button" class="btn btn-outline-default custom-picker"
|
||||||
<select id="lang_select" class="form-control selectpicker show-tick custom-picker" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
|
onclick="$('option', $('#lang_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{
|
||||||
|
translate('panelConfig', 'enableLang', data['lang']) }}</button>
|
||||||
|
<select id="lang_select" class="form-control selectpicker show-tick custom-picker"
|
||||||
|
data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
|
||||||
{% for lang in data['all_languages'] %}
|
{% for lang in data['all_languages'] %}
|
||||||
{% if lang in item[1] %}
|
{% if lang in item[1] %}
|
||||||
<option selected>{{lang}}</option>
|
<option selected>{{lang}}</option>
|
||||||
@ -83,12 +86,17 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
</select>
|
</select>
|
||||||
<textarea id="disabled_lang" name="{{item[0]}}" class="form-control list hidden" rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}" hidden>{{','.join(item[1])}}</textarea>
|
<textarea id="disabled_lang" name="{{item[0]}}" class="form-control list hidden"
|
||||||
|
rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}"
|
||||||
|
hidden>{{','.join(item[1])}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
{% elif item[0] == 'monitored_mounts'%}
|
{% elif item[0] == 'monitored_mounts'%}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#mount_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{ translate('panelConfig', 'noMounts', data['lang']) }}</button>
|
<button type="button" class="btn btn-outline-default custom-picker"
|
||||||
<select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
|
onclick="$('option', $('#mount_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{
|
||||||
|
translate('panelConfig', 'noMounts', data['lang']) }}</button>
|
||||||
|
<select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas"
|
||||||
|
data-tick-icon="fa-check" multiple data-style="custom-picker">
|
||||||
{% for mount in data['all_partitions'] %}
|
{% for mount in data['all_partitions'] %}
|
||||||
{% if mount in item[1] %}
|
{% if mount in item[1] %}
|
||||||
<option selected>{{mount}}</option>
|
<option selected>{{mount}}</option>
|
||||||
@ -97,10 +105,13 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
</select>
|
</select>
|
||||||
<textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden" rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}" hidden>{{','.join(item[1])}}</textarea>
|
<textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden"
|
||||||
|
rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}"
|
||||||
|
hidden>{{','.join(item[1])}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
{% elif isinstance(item[1], list) %}
|
{% elif isinstance(item[1], list) %}
|
||||||
<textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}" class="form-control list">{{','.join(item[1])}}</textarea>
|
<textarea id="{{item[0]}}" value="{{','.join(item[1])}}" type="text" name="{{item[0]}}"
|
||||||
|
class="form-control list">{{','.join(item[1])}}</textarea>
|
||||||
{% elif isinstance(item[1], bool) %}
|
{% elif isinstance(item[1], bool) %}
|
||||||
<div style="margin-left: 30px;">
|
<div style="margin-left: 30px;">
|
||||||
{% if item[1] == True %}
|
{% if item[1] == True %}
|
||||||
@ -116,9 +127,11 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
{% elif isinstance(item[1], int) %}
|
{% elif isinstance(item[1], int) %}
|
||||||
<input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="1" min="0" required>
|
<input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}"
|
||||||
|
step="1" min="0" required>
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="2" min="0" required>
|
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}"
|
||||||
|
step="2" min="0" required>
|
||||||
{% end %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -156,36 +169,66 @@
|
|||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
<script>
|
||||||
$("#config-form").submit(function (e) {
|
function replacer(key, value) {
|
||||||
let uuid = uuidv4();
|
if (key == "disabled_language_files") {
|
||||||
var token = getCookie("_xsrf")
|
if (value == 0) {
|
||||||
|
return []
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof value == "boolean") {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return (isNaN(value) ? value : +value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#config-form").on("submit", async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
|
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
|
||||||
/* Convert multiple select to text list */
|
const token = getCookie("_xsrf")
|
||||||
let selected_Lang = $('#lang_select').val();
|
let configForm = document.getElementById("config-form");
|
||||||
$('#disabled_lang').val(selected_Lang);
|
|
||||||
|
|
||||||
let mounts = $('#mount_select').val();
|
let formData = new FormData(configForm);
|
||||||
$('#monitored_mounts').val(mounts);
|
formData.delete("disabled_lang");
|
||||||
|
formData.delete("lang_select");
|
||||||
|
|
||||||
let class_list = document.getElementsByClassName("list");
|
//Create an object from the form data entries
|
||||||
let form_json = convertFormToJSON($("#config-form"));
|
let formDataObject = Object.fromEntries(formData.entries());
|
||||||
for (let i = 0; i < class_list.length; i++) {
|
//We need to make sure these are sent regardless of whether or not they're checked
|
||||||
let str = String($(class_list.item(i)).val())
|
formDataObject.disabled_language_files = $('#lang_select').val();
|
||||||
form_json[$(class_list.item(i)).attr("name")] = uuid + "," + str.replace(/\s/g, '');
|
formDataObject.monitored_mounts = $('#mount_select').val();
|
||||||
};
|
formDataObject.keywords = $('#keywords').val().split(",");
|
||||||
form_json['uuid'] = uuid;
|
$('#config-form input[type="radio"]:checked').each(function () {
|
||||||
$.ajax({
|
if ($(this).val() == 'True') {
|
||||||
type: "POST",
|
formDataObject[this.name] = true;
|
||||||
headers: { 'X-XSRFToken': token },
|
} else {
|
||||||
dataType: "text",
|
formDataObject[this.name] = false;
|
||||||
url: '/panel/config_json',
|
}
|
||||||
data: form_json,
|
|
||||||
success: function (data) {
|
|
||||||
$("#submit-status").html('<i class="fa fa-check"></i>');
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
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/crafty/config/`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
body: formDataJsonString,
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
$("#submit-status").html('<i class="fa fa-check"></i>');
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function uuidv4() {
|
function uuidv4() {
|
||||||
@ -257,7 +300,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('.clear-comm').click(function () {
|
$('.clear-comm').click(function () {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: { 'X-XSRFToken': token },
|
||||||
@ -268,7 +311,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
$('.delete-photo').click(function () {
|
$('.delete-photo').click(function () {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
let photo = $('#photo').find(":selected").val();
|
let photo = $('#photo').find(":selected").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -281,7 +324,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
$('.select-photo').click(function () {
|
$('.select-photo').click(function () {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
let photo = $('#photo').find(":selected").val();
|
let photo = $('#photo').find(":selected").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
|
@ -62,11 +62,14 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div id="upload_input" class="input-group">
|
<div id="upload_input" class="input-group">
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
<input type="file" class="custom-file-input" id="file" name="file" multiple="false" required>
|
<input type="file" class="custom-file-input" id="file" name="file" multiple="false"
|
||||||
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('customLogin', 'labelLoginImage', data['lang']) }}</label>
|
required>
|
||||||
|
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('customLogin',
|
||||||
|
'labelLoginImage', data['lang']) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()" disabled>UPLOAD</button>
|
<button type="button" class="btn btn-info upload-button" id="upload-button"
|
||||||
|
onclick="sendFile()" disabled>UPLOAD</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -81,7 +84,8 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="photo" class="col-sm-6 col-form-label">Selected Background Image</label>
|
<label for="photo" class="col-sm-6 col-form-label">Selected Background Image</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<select class="form-select form-control form-control-lg select-css form-control-plaintext" id="photo" name="photo" form="photo_form" onchange="updateBackgroundPreview()">
|
<select class="form-select form-control form-control-lg select-css form-control-plaintext"
|
||||||
|
id="photo" name="photo" form="photo_form" onchange="updateBackgroundPreview()">
|
||||||
{% for image in data["backgrounds"] %}
|
{% for image in data["backgrounds"] %}
|
||||||
<option value="{{image}}">{{image}}</option>
|
<option value="{{image}}">{{image}}</option>
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -90,7 +94,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="photo_loading" class="form-group" hidden>
|
<div id="photo_loading" class="form-group" hidden>
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i class="fa-solid fa-spinner"></i></div>
|
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
|
||||||
|
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i
|
||||||
|
class="fa-solid fa-spinner"></i></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
@ -98,11 +104,13 @@
|
|||||||
data['lang']) }}</label>
|
data['lang']) }}</label>
|
||||||
<label class="col-sm-1" id="opacityValue">{{ data['login_opacity'] }}%</label>
|
<label class="col-sm-1" id="opacityValue">{{ data['login_opacity'] }}%</label>
|
||||||
<div class="range col-sm-8">
|
<div class="range col-sm-8">
|
||||||
<input type="range" class="form-control-range" id="modal_opacity" name="modal_opacity" onchange="previewOpacity()" min="0" max="100" value="{{ data['login_opacity'] }}">
|
<input type="range" class="form-control-range" id="modal_opacity" name="modal_opacity"
|
||||||
|
onchange="previewOpacity()" min="0" max="100" value="{{ data['login_opacity'] }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="login_preview" style="position: relative;">
|
<div id="login_preview" style="position: relative;">
|
||||||
<img id="bg-preview" src="../../static/assets/images/auth/{{ data['background'] }}" class="img-fluid" alt="Responsive image">
|
<img id="bg-preview" src="../../static/assets/images/auth/{{ data['background'] }}"
|
||||||
|
class="img-fluid" alt="Responsive image">
|
||||||
<div id="login-form-preview">
|
<div id="login-form-preview">
|
||||||
<div id="login-form-background" class="auto-form-wrapper login-modal">
|
<div id="login-form-background" class="auto-form-wrapper login-modal">
|
||||||
<div class="text-center auto-form-logo">
|
<div class="text-center auto-form-logo">
|
||||||
@ -166,17 +174,20 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="login_form_data">
|
<div id="login_form_data">
|
||||||
<input type="hidden" name="_xsrf" value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
|
<input type="hidden" name="_xsrf"
|
||||||
|
value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="label">Username</label>
|
<label class="label">Username</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control login-text-input login-input" placeholder="Username" name="username" id="username" required="true" disabled>
|
<input type="text" class="form-control login-text-input login-input"
|
||||||
|
placeholder="Username" name="username" id="username" required="true" disabled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="label">Password</label>
|
<label class="label">Password</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" class="form-control login-text-input login-input" placeholder="Password" name="password" id="password" required="true" disabled>
|
<input type="password" class="form-control login-text-input login-input"
|
||||||
|
placeholder="Password" name="password" id="password" required="true" disabled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -195,7 +206,8 @@
|
|||||||
<a href="#" class="text-small forgot-password" disabled>Forgot Password</a>
|
<a href="#" class="text-small forgot-password" disabled>Forgot Password</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-block text-center my-3">
|
<div class="text-block text-center my-3">
|
||||||
<span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control
|
<span class="text-small font-weight-semibold"><a
|
||||||
|
href="https://craftycontrol.com/">Crafty Control
|
||||||
4.0.20</a> </span>
|
4.0.20</a> </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -297,33 +309,50 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.delete-photo').click(function () {
|
$('.delete-photo').click(async function () {
|
||||||
var token = getCookie("_xsrf")
|
|
||||||
let photo = $('#photo').find(":selected").val();
|
let photo = $('#photo').find(":selected").val();
|
||||||
$.ajax({
|
const token = getCookie("_xsrf")
|
||||||
type: "POST",
|
let res = await fetch(`/api/v2/crafty/config/customize`, {
|
||||||
headers: { 'X-XSRFToken': token },
|
method: 'DELETE',
|
||||||
url: '/ajax/delete_photo?photo=' + encodeURIComponent(photo),
|
headers: {
|
||||||
success: function (data) {
|
'X-XSRFToken': token
|
||||||
location.reload();
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "photo": photo }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$('.select-photo').click(function () {
|
$('.select-photo').click(async function () {
|
||||||
var token = getCookie("_xsrf")
|
|
||||||
let photo = $('#photo').find(":selected").val();
|
let photo = $('#photo').find(":selected").val();
|
||||||
let opacity = $('#modal_opacity').val();
|
let opacity = $('#modal_opacity').val();
|
||||||
let enc_photo = encodeURIComponent(photo);
|
console.log(JSON.stringify({ "photo": photo, "opacity": opacity }))
|
||||||
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/crafty/config/customize`, {
|
||||||
type: "POST",
|
method: 'PATCH',
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: {
|
||||||
url: '/ajax/select_photo?photo=' + enc_photo + '&opacity=' + opacity,
|
'X-XSRFToken': token
|
||||||
success: function (data) {
|
|
||||||
window.location.reload();
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "photo": photo, "opacity": opacity }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -1,106 +1,103 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<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" />
|
||||||
|
|
||||||
<head>
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<!-- Required meta tags -->
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta charset="utf-8">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="apple-mobile-web-app-title" content="Crafty" />
|
||||||
<title>Crafty Controller</title>
|
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png" />
|
||||||
<!-- 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">
|
|
||||||
|
|
||||||
|
<!-- endinject -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<!-- Plugin css for this page -->
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<!-- End Plugin css for this page -->
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<!-- Layout styles -->
|
||||||
<meta name="apple-mobile-web-app-title" content="Crafty">
|
<link rel="stylesheet" href="/static/assets/css/dark/style.css" />
|
||||||
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
|
<!-- 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">
|
||||||
<!-- endinject -->
|
<div class="container-scroller">
|
||||||
<!-- Plugin css for this page -->
|
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||||
<!-- End Plugin css for this page -->
|
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one" >
|
||||||
<!-- Layout styles -->
|
<div class="row w-100">
|
||||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
<div class="col-lg-4 mx-auto">
|
||||||
<!-- End Layout styles -->
|
<div class="auto-form-wrapper">
|
||||||
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
|
<div class="text-center">
|
||||||
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
|
<img alt="Crafty Logo" src="/static/assets/images/logo_long.svg" /><br /><br />
|
||||||
</head>
|
<div class="col-sm-12 grid-margin stretch-card">
|
||||||
<style>
|
<div class="card card-statistics social-card google-card card-colored" >
|
||||||
.auth.auth-bg-1 {
|
<div class="card-body">
|
||||||
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
|
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name" >
|
||||||
url("/static/assets/images/auth/login_1.jpg");
|
{{ translate('accessDenied', 'accessDenied', data['lang']) }}
|
||||||
background-size: cover;
|
</h4>
|
||||||
}
|
<h5 class="headline font-weight-medium">
|
||||||
</style>
|
{{ translate('accessDenied', 'noAccess', data['lang']) }}
|
||||||
|
</h5>
|
||||||
<body class="dark-theme">
|
<p class="mb-2 comment font-weight-light">
|
||||||
<div class="container-scroller">
|
{{ translate('accessDenied', 'contactAdmin',
|
||||||
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
data['lang']) }}<br /><br />
|
||||||
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
|
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE" > {{ translate('accessDenied', 'contact', data['lang']) }}</a>
|
||||||
<div class="row w-100">
|
</p>
|
||||||
<div class="col-lg-4 mx-auto">
|
</div>
|
||||||
|
|
||||||
<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 google-card card-colored">
|
|
||||||
<div class="card-body">
|
|
||||||
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('accessDenied',
|
|
||||||
'accessDenied', data['lang']) }}</h4>
|
|
||||||
<h5 class="headline font-weight-medium">{{ translate('accessDenied', 'noAccess', data['lang']) }}
|
|
||||||
</h5>
|
|
||||||
<p class="mb-2 comment font-weight-light">
|
|
||||||
{{ translate('accessDenied', 'contactAdmin', data['lang']) }}<br /><br />
|
|
||||||
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{
|
|
||||||
translate('accessDenied', 'contact', data['lang']) }}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- content-wrapper ends -->
|
||||||
</div>
|
</div>
|
||||||
<!-- content-wrapper ends -->
|
<!-- page-body-wrapper ends -->
|
||||||
</div>
|
</div>
|
||||||
<!-- page-body-wrapper ends -->
|
<!-- container-scroller -->
|
||||||
</div>
|
<!-- plugins:js -->
|
||||||
<!-- container-scroller -->
|
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||||
<!-- plugins:js -->
|
<!-- endinject -->
|
||||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
<!-- inject:js -->
|
||||||
<!-- endinject -->
|
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||||
<!-- inject:js -->
|
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
<script src="/static/assets/js/shared/misc.js"></script>
|
||||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
<script src="/static/assets/js/shared/settings.js"></script>
|
||||||
<script src="/static/assets/js/shared/misc.js"></script>
|
<script src="/static/assets/js/shared/todolist.js"></script>
|
||||||
<script src="/static/assets/js/shared/settings.js"></script>
|
<!-- endinject -->
|
||||||
<script src="/static/assets/js/shared/todolist.js"></script>
|
<script>
|
||||||
<!-- endinject -->
|
$(document).ready(function () {
|
||||||
<script>
|
let login_opacity_div = document.getElementById("login_opacity");
|
||||||
$(document).ready(function () {
|
let opacity = login_opacity_div.getAttribute("data-value");
|
||||||
let login_opacity_div = document.getElementById('login_opacity');
|
document.getElementById("login-form-background").style.background =
|
||||||
let opacity = login_opacity_div.getAttribute('data-value');
|
"rgb(34, 36, 55, " + opacity / 100 + ")";
|
||||||
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
|
//Register Service worker for mobile app
|
||||||
//Register Service worker for mobile app
|
if ("serviceWorker" in navigator) {
|
||||||
if ('serviceWorker' in navigator) {
|
navigator.serviceWorker
|
||||||
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
|
.register("/static/assets/js/shared/service-worker.js", {
|
||||||
.then(function (registration) {
|
scope: "/",
|
||||||
console.error('Service Worker Registered');
|
})
|
||||||
});
|
.then(function (registration) {
|
||||||
}
|
console.log("Service Worker Registered");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
</script>
|
});
|
||||||
</body>
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -326,24 +326,30 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#server-path").submit(function (e) {
|
$("#server-path").submit(async function (e) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
|
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
|
||||||
|
|
||||||
let path = $("#global_server_path").val();
|
let path = $("#global_server_path").val();
|
||||||
let encoded = encodeURIComponent(path);
|
let res = await fetch(`/api/v2/crafty/config/servers_dir`, {
|
||||||
console.log(path)
|
method: 'PATCH',
|
||||||
$.ajax({
|
headers: {
|
||||||
type: "POST",
|
'X-XSRFToken': token
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
dataType: "text",
|
|
||||||
url: '/ajax/update_server_dir',
|
|
||||||
data: {
|
|
||||||
"server_dir": encoded,
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "new_dir": path }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -49,10 +49,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="">
|
<div class="">
|
||||||
<form id="role_form" class="forms-sample" method="post" action="{{ '/panel/add_role' if data['new_role'] else '/panel/edit_role' }}">
|
<form id="role_form" class="forms-sample">
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<input type="hidden" name="id" value="{{ data['role']['role_id'] }}">
|
|
||||||
<input type="hidden" name="subpage" value="config">
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||||
@ -61,7 +58,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="role_name">{{ translate('rolesConfig', 'roleName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('rolesConfig', 'roleDesc', data['lang']) }}</small> </label>
|
<label for="role_name">{{ translate('rolesConfig', 'roleName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('rolesConfig', 'roleDesc', data['lang']) }}</small> </label>
|
||||||
<input type="text" class="form-control" name="role_name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" >
|
<input type="text" class="form-control" name="name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" >
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
@ -188,11 +185,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ server['server_name'] }}</td>
|
<td>{{ server['server_name'] }}</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" class="" onclick="enable_disable(event)" data-id="{{server['server_id']}}"
|
<input type="checkbox" class="access" onclick="enable_disable(event)" data-id="{{server['server_id']}}"
|
||||||
id="server_{{ server['server_id'] }}_access"
|
id="server_{{ server['server_id'] }}_access"
|
||||||
name="server_{{ server['server_id'] }}_access"
|
name="server_{{ server['server_id'] }}_access"
|
||||||
{{ 'checked' if server['server_id'] in data['role']['servers'] else '' }}
|
{{ 'checked' if server['server_id'] in data['role']['servers'] else '' }}
|
||||||
autocomplete="off" value="1">
|
autocomplete="off" value="1" form="dummy">
|
||||||
</td>
|
</td>
|
||||||
{% for permission in data['permissions_all'] %}
|
{% for permission in data['permissions_all'] %}
|
||||||
{% if server['server_id'] in data['role']['servers'] %}
|
{% if server['server_id'] in data['role']['servers'] %}
|
||||||
@ -201,14 +198,14 @@
|
|||||||
id="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
id="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
||||||
name="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
name="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
||||||
{{ 'checked' if permission in data['permissions_dict'].get(server['server_id'], []) else '' }}
|
{{ 'checked' if permission in data['permissions_dict'].get(server['server_id'], []) else '' }}
|
||||||
autocomplete="off" value="1">
|
autocomplete="off" value="1" form="dummy">
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" class="{{server['server_id']}}_perms"
|
<input type="checkbox" class="{{server['server_id']}}_perms"
|
||||||
id="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
id="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
||||||
name="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
name="permission_{{ server['server_id'] }}_{{ permission.name }}"
|
||||||
autocomplete="off" value="1" disabled>
|
autocomplete="off" value="1" disabled form="dummy">
|
||||||
</td>
|
</td>
|
||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -284,7 +281,7 @@
|
|||||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a><br />
|
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a><br />
|
||||||
<small>{{ translate('rolesConfig', 'doesNotExist', data['lang']) }}</small>
|
<small>{{ translate('rolesConfig', 'doesNotExist', data['lang']) }}</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a>
|
<button onclick="del_role()" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</button>
|
||||||
{% end %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -341,24 +338,87 @@
|
|||||||
console.log( "ready!" );
|
console.log( "ready!" );
|
||||||
});
|
});
|
||||||
const roleId = new URLSearchParams(document.location.search).get('id');
|
const roleId = new URLSearchParams(document.location.search).get('id');
|
||||||
|
|
||||||
|
function replacer(key, value) {
|
||||||
|
if (key === "permissions"){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (key === "servers" && value.length === 0){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value == "boolean") {
|
||||||
|
console.log(value);
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return (isNaN(value) ? value : +value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$("#config_form").on("submit", async function (e) {
|
async function del_role(){
|
||||||
|
const token = getCookie("_xsrf")
|
||||||
|
let res = await fetch(`/api/v2/roles/${roleId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.href = "/panel/panel_config";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#role_form").on("submit", async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
let configForm = document.getElementById("config_form");
|
let roleForm = document.getElementById("role_form");
|
||||||
|
|
||||||
let formData = new FormData(configForm);
|
let server_ids = $('.access').map(function() {
|
||||||
|
if ($(this).is(':checked')){
|
||||||
|
return $(this).data('id');
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
let servers = []
|
||||||
|
for(i=0; i < server_ids.length; i++){
|
||||||
|
let arrchecked = $(`.${server_ids[i]}_perms`).map(function() {
|
||||||
|
if(this.checked){
|
||||||
|
return "1";
|
||||||
|
}else{
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
servers.push({"server_id": server_ids[i], "permissions": arrchecked.join("")});
|
||||||
|
}
|
||||||
|
console.log(servers)
|
||||||
|
|
||||||
|
let formData = new FormData(roleForm);
|
||||||
//Create an object from the form data entries
|
//Create an object from the form data entries
|
||||||
let formDataObject = Object.fromEntries(formData.entries());
|
let formDataObject = Object.fromEntries(formData.entries());
|
||||||
let send_object = Object()
|
formDataObject.servers = servers;
|
||||||
send_object.servers = []
|
console.log(formDataObject);
|
||||||
send_object.name = formDataObject.role_name
|
|
||||||
|
//We need to make sure these are sent regardless of whether or not they're checked
|
||||||
|
|
||||||
// Format the plain form data as JSON
|
// Format the plain form data as JSON
|
||||||
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||||
|
|
||||||
let res = await fetch(`/api/v2/roles/${roleId}`, {
|
console.log(formDataJsonString);
|
||||||
method: 'PATCH',
|
|
||||||
|
let url = `/api/v2/roles/`
|
||||||
|
let method = 'POST'
|
||||||
|
if (roleId){
|
||||||
|
url = `/api/v2/roles/${roleId}`
|
||||||
|
method = 'PATCH'
|
||||||
|
}
|
||||||
|
let res = await fetch(url, {
|
||||||
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
'X-XSRFToken': token
|
'X-XSRFToken': token
|
||||||
},
|
},
|
||||||
@ -366,14 +426,14 @@
|
|||||||
});
|
});
|
||||||
let responseData = await res.json();
|
let responseData = await res.json();
|
||||||
if (responseData.status === "ok") {
|
if (responseData.status === "ok") {
|
||||||
window.location.reload();
|
window.location.href = "/panel/panel_config";
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bootbox.alert({
|
bootbox.alert({
|
||||||
title: responseData.error,
|
title: responseData.error,
|
||||||
message: responseData.error_data
|
message: responseData.error_data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,13 +58,11 @@ data['lang']) }}{% end %}
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 col-sm-12">
|
<div class="col-md-6 col-sm-12">
|
||||||
{% if data['new_user'] %}
|
{% if data['new_user'] %}
|
||||||
<form id="user_form" class="forms-sample" method="post" action="/panel/add_user">
|
<form id="user_form" class="forms-sample">
|
||||||
{% else %}
|
{% else %}
|
||||||
<form id="user_form" class="forms-sample" method="post" action="/panel/edit_user">
|
<form id="user_form" class="forms-sample">
|
||||||
{% end %}
|
{% end %}
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
|
|
||||||
<input type="hidden" name="subpage" value="config">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -85,7 +83,7 @@ data['lang']) }}{% end %}
|
|||||||
}}<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang']) }}
|
}}<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang']) }}
|
||||||
</small> </label>
|
</small> </label>
|
||||||
<input type="password" class="form-control" name="password0" id="password0" value=""
|
<input type="password" class="form-control" name="password0" id="password0" value=""
|
||||||
autocomplete="new-password" data-lpignore="true" placeholder="Password">
|
autocomplete="new-password" data-lpignore="true" placeholder="Password" form="dummy">
|
||||||
<span class="passwords-match" ,
|
<span class="passwords-match" ,
|
||||||
data-content="{{ translate('panelConfig', 'match', data['lang']) }}" ,
|
data-content="{{ translate('panelConfig', 'match', data['lang']) }}" ,
|
||||||
data-placement="right"></span>
|
data-placement="right"></span>
|
||||||
@ -95,7 +93,7 @@ data['lang']) }}{% end %}
|
|||||||
<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang'])
|
<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang'])
|
||||||
}}</small> </label>
|
}}</small> </label>
|
||||||
<input type="password" class="form-control" name="password1" id="password1" value=""
|
<input type="password" class="form-control" name="password1" id="password1" value=""
|
||||||
autocomplete="new-password" data-lpignore="true" placeholder="Repeat Password">
|
autocomplete="new-password" data-lpignore="true" placeholder="Repeat Password" form="dummy">
|
||||||
<span class="passwords-match" ,
|
<span class="passwords-match" ,
|
||||||
data-content="{{ translate('panelConfig', 'match', data['lang']) }}" ,
|
data-content="{{ translate('panelConfig', 'match', data['lang']) }}" ,
|
||||||
data-placement="right"></span>
|
data-placement="right"></span>
|
||||||
@ -111,7 +109,7 @@ data['lang']) }}{% end %}
|
|||||||
<label class="form-label" for="language">{{ translate('userConfig', 'userLang', data['lang'])
|
<label class="form-label" for="language">{{ translate('userConfig', 'userLang', data['lang'])
|
||||||
}}</label>
|
}}</label>
|
||||||
<select class="form-select form-control form-control-lg select-css" id="language"
|
<select class="form-select form-control form-control-lg select-css" id="language"
|
||||||
name="language" form="user_form">
|
name="lang" form="user_form">
|
||||||
{% for lang in data['languages'] %}
|
{% for lang in data['languages'] %}
|
||||||
{% if not 'incomplete' in lang %}
|
{% if not 'incomplete' in lang %}
|
||||||
<option value="{{lang}}">{{lang}}</option>
|
<option value="{{lang}}">{{lang}}</option>
|
||||||
@ -182,18 +180,18 @@ data['lang']) }}{% end %}
|
|||||||
<td>
|
<td>
|
||||||
{% if role.role_id in data['user']['roles'] %}
|
{% if role.role_id in data['user']['roles'] %}
|
||||||
{% if role.manager == data['exec_user'] or data['superuser'] %}
|
{% if role.manager == data['exec_user'] or data['superuser'] %}
|
||||||
<input type="checkbox" class="form-check-input"
|
<input type="checkbox" class="form-check-input role_check"
|
||||||
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
|
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
|
||||||
checked="" value="1">
|
checked="" value="{{role.role_id}}" form="dummy">
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="checkbox" class="form-check-input"
|
<input type="checkbox" class="form-check-input role_check"
|
||||||
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
|
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
|
||||||
checked="" value="1" disabled>
|
checked="" value="{{role.role_id}}" disabled form="dummy">
|
||||||
{% end %}
|
{% end %}
|
||||||
{% elif data['superuser'] or role.manager == data['exec_user'] %}
|
{% elif data['superuser'] or role.manager == data['exec_user'] %}
|
||||||
<input type="checkbox" class="form-check-input"
|
<input type="checkbox" class="form-check-input role_check"
|
||||||
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
|
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
|
||||||
value="1">
|
value="{{role.role_id}}" form="dummy">
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
@ -219,7 +217,7 @@ data['lang']) }}{% end %}
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table id="permissions" aria-describedby="User Crafty Permissions" class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="rounded">
|
<tr class="rounded">
|
||||||
<th>{{ translate('userConfig', 'permName', data['lang']) }}</th>
|
<th>{{ translate('userConfig', 'permName', data['lang']) }}</th>
|
||||||
@ -233,16 +231,16 @@ data['lang']) }}{% end %}
|
|||||||
<td>{{ permission.name }}</td>
|
<td>{{ permission.name }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if permission in data['permissions_list'] %}
|
{% if permission in data['permissions_list'] %}
|
||||||
<input type="checkbox" class="form-check-input" id="permission_{{ permission.name }}"
|
<input type="checkbox" class="form-check-input perm-name" id="permission_{{ permission.name }}"
|
||||||
name="permission_{{ permission.name }}" checked="" value="1">
|
name="permission_{{ permission.name }}" checked="" value="1" data-perm="{{permission.name}}" form="dummy">
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="checkbox" class="form-check-input" id="permission_{{ permission.name }}"
|
<input type="checkbox" class="form-check-input perm-name" id="permission_{{ permission.name }}"
|
||||||
name="permission_{{ permission.name }}" value="1">
|
name="permission_{{ permission.name }}" value="1" data-perm="{{permission.name}}" form="dummy">
|
||||||
{% end %}
|
{% end %}
|
||||||
</td>
|
</td>
|
||||||
<td><input type="text" class="form-control" name="quantity_{{ permission.name }}"
|
<td><input type="text" class="form-control" name="quantity_{{ permission.name }}"
|
||||||
id="quantity_{{ permission.name }}"
|
id="quantity_{{ permission.name }}"
|
||||||
value="{{ data['quantity_server'][permission.name] }}"></td>
|
value="{{ data['quantity_server'][permission.name] }}" data-perm="{{permission.name}}" form="dummy"></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% end %}
|
{% end %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -287,7 +285,7 @@ data['lang']) }}{% end %}
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-success mr-2" onclick="submit_user(event);"><i class="fas fa-save"></i> {{
|
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{
|
||||||
translate('panelConfig', 'save', data['lang']) }}</button>
|
translate('panelConfig', 'save', data['lang']) }}</button>
|
||||||
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light"><i
|
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light"><i
|
||||||
class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}</button>
|
class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}</button>
|
||||||
@ -363,9 +361,12 @@ data['lang']) }}{% end %}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
let password0 = document.getElementById("password0").value
|
let password0 = document.getElementById("password0").value;
|
||||||
let password1 = document.getElementById("password1").value
|
let password1 = document.getElementById("password1").value;
|
||||||
if (password0 != password1) {
|
if (password0 === "" && password1 === "" && userId){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else if (password0 != password1) {
|
||||||
$('.passwords-match').popover('show');
|
$('.passwords-match').popover('show');
|
||||||
$('.popover-body').click(function () {
|
$('.popover-body').click(function () {
|
||||||
$('.passwords-match').popover("hide");
|
$('.passwords-match').popover("hide");
|
||||||
@ -376,11 +377,103 @@ data['lang']) }}{% end %}
|
|||||||
$("#password1").css("outline", "1px solid red");
|
$("#password1").css("outline", "1px solid red");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return password1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function replacer(key, value) {
|
||||||
|
if (typeof value == "boolean" || key === "email" || key === "permissions" || key === "roles") {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
console.log(key, value)
|
||||||
|
return (isNaN(value) ? value : +value);
|
||||||
|
}
|
||||||
|
}
|
||||||
const userId = new URLSearchParams(document.location.search).get('id')
|
const userId = new URLSearchParams(document.location.search).get('id')
|
||||||
|
$("#user_form").on("submit", async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let password = validateForm();
|
||||||
|
if (!password){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const token = getCookie("_xsrf")
|
||||||
|
let userForm = document.getElementById("user_form");
|
||||||
|
|
||||||
|
let disabled_flag = false;
|
||||||
|
let roles = $('.role_check').map(function() {
|
||||||
|
if ($(this).attr("disabled")){
|
||||||
|
disabled_flag = true;
|
||||||
|
}
|
||||||
|
if ($(this).is(':checked')){
|
||||||
|
return $(this).val();
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
let avail_permissions = $('.perm-name').map(function() {
|
||||||
|
return $(this).data("perm");
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
permissions = []
|
||||||
|
for(i=0; i < avail_permissions.length; i++){
|
||||||
|
permissions.push({"name": avail_permissions[i], "quantity": $(`#quantity_${avail_permissions[i]}`).val(), "enabled": $(`#permission_${avail_permissions[i]}`).is(':checked')})
|
||||||
|
}
|
||||||
|
console.log(permissions);
|
||||||
|
|
||||||
|
let formData = new FormData(userForm);
|
||||||
|
//Create an object from the form data entries
|
||||||
|
let formDataObject = Object.fromEntries(formData.entries());
|
||||||
|
if (!disabled_flag){
|
||||||
|
formDataObject.roles = roles;
|
||||||
|
}
|
||||||
|
if ($("#permissions").length){
|
||||||
|
formDataObject.permissions = permissions;
|
||||||
|
}
|
||||||
|
if(typeof password === "string"){
|
||||||
|
formDataObject.password = password;
|
||||||
|
}
|
||||||
|
formDataObject.enabled = $("#enabled").is(":checked");
|
||||||
|
if ($("#superuser").is(":enabled")){
|
||||||
|
formDataObject.superuser = $("#superuser").is(":checked");
|
||||||
|
}
|
||||||
|
formDataObject.hints = $("#hints").is(":checked");
|
||||||
|
console.log(formDataObject);
|
||||||
|
|
||||||
|
//We need to make sure these are sent regardless of whether or not they're checked
|
||||||
|
|
||||||
|
// Format the plain form data as JSON
|
||||||
|
let formDataJsonString = JSON.stringify(formDataObject, replacer);
|
||||||
|
|
||||||
|
console.log(formDataJsonString);
|
||||||
|
if (userId){
|
||||||
|
url = `/api/v2/users/${userId}`
|
||||||
|
method = 'PATCH'
|
||||||
|
}else{
|
||||||
|
url = `/api/v2/users/`
|
||||||
|
method = 'POST'
|
||||||
|
}
|
||||||
|
let res = await fetch(url, {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
body: formDataJsonString,
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.href = "/panel/panel_config";
|
||||||
|
} else {
|
||||||
|
if (responseData.hasOwnProperty("error_data")){
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
bootbox.alert(responseData.error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
$(".delete-user").click(function () {
|
$(".delete-user").click(function () {
|
||||||
var file_to_del = $(this).data("file");
|
var file_to_del = $(this).data("file");
|
||||||
|
|
||||||
@ -398,10 +491,26 @@ data['lang']) }}{% end %}
|
|||||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
callback: function (result) {
|
callback: async function (result) {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
location.href = "/panel/remove_user?id=" + userId;
|
const token = getCookie("_xsrf")
|
||||||
|
let res = await fetch(`/api/v2/users/${userId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.href = "/panel/panel_config";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -86,17 +86,14 @@
|
|||||||
apikey.server_permissions }}
|
apikey.server_permissions }}
|
||||||
{{ translate('apiKeys', 'crafty', data['lang']) }} {{
|
{{ translate('apiKeys', 'crafty', data['lang']) }} {{
|
||||||
apikey.crafty_permissions }}</td>
|
apikey.crafty_permissions }}</td>
|
||||||
<td>
|
<td><button class="btn btn-danger delete-api-key"
|
||||||
<button class="btn btn-danger delete-api-key"
|
|
||||||
data-key-id="{{ apikey.token_id }}"
|
data-key-id="{{ apikey.token_id }}"
|
||||||
data-key-name="{{ apikey.name }}">{{
|
data-key-name="{{ apikey.name }}">{{translate('panelConfig',
|
||||||
translate('panelConfig', 'delete', data['lang'])
|
'delete', data['lang'])}}</button>
|
||||||
}}</button>
|
|
||||||
<button class="btn btn-outline-primary get-a-token"
|
<button class="btn btn-outline-primary get-a-token"
|
||||||
data-key-id="{{ apikey.token_id }}"
|
data-key-id="{{ apikey.token_id }}"
|
||||||
data-key-name="{{ apikey.name }}">{{
|
data-key-name="{{ apikey.name }}">{{translate('apiKeys',
|
||||||
translate('apiKeys', 'getToken', data['lang']) }}
|
'getToken', data['lang'])}}</button>
|
||||||
</button>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -115,10 +112,7 @@
|
|||||||
'createNew', data['lang']) }}</h4>
|
'createNew', data['lang']) }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="user_form" class="forms-sample" method="post"
|
<form id="user_api_form" class="forms-sample">
|
||||||
action="/panel/edit_user_apikeys">
|
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label" for="username">{{ translate('apiKeys', 'name',
|
<label class="form-label" for="username">{{ translate('apiKeys', 'name',
|
||||||
@ -142,7 +136,7 @@
|
|||||||
}}</label>
|
}}</label>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" class=""
|
<input type="checkbox" class="server_perm"
|
||||||
id="permission_{{ permission.name }}"
|
id="permission_{{ permission.name }}"
|
||||||
name="permission_{{ permission.name }}" value="1">
|
name="permission_{{ permission.name }}" value="1">
|
||||||
</td>
|
</td>
|
||||||
@ -154,7 +148,7 @@
|
|||||||
}}</label>
|
}}</label>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" class=""
|
<input type="checkbox" class="crafty_perm"
|
||||||
id="permission_{{ permission.name }}"
|
id="permission_{{ permission.name }}"
|
||||||
name="permission_{{ permission.name }}" value="1">
|
name="permission_{{ permission.name }}" value="1">
|
||||||
</td>
|
</td>
|
||||||
@ -201,56 +195,122 @@
|
|||||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||||
return r ? r[1] : undefined;
|
return r ? r[1] : undefined;
|
||||||
}
|
}
|
||||||
|
const userId = new URLSearchParams(document.location.search).get('id')
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("#user_api_form").on("submit", async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const token = getCookie("_xsrf")
|
||||||
|
let apiForm = document.getElementById("user_api_form");
|
||||||
|
|
||||||
|
let formData = new FormData(apiForm);
|
||||||
|
|
||||||
|
//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.disabled_language_files = $('#lang_select').val();
|
||||||
|
$('#user_api_form input[type="checkbox"]:checked').each(function () {
|
||||||
|
if ($(this).val() == 'True') {
|
||||||
|
formDataObject[this.name] = true;
|
||||||
|
} else {
|
||||||
|
formDataObject[this.name] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let server_permissions = $('.server_perm').map(function () {
|
||||||
|
if (this.checked) {
|
||||||
|
return "1";
|
||||||
|
} else {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
server_permissions = server_permissions.join("");
|
||||||
|
|
||||||
|
let crafty_permissions = $('.crafty_perm').map(function () {
|
||||||
|
if (this.checked) {
|
||||||
|
return "1";
|
||||||
|
} else {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
crafty_permissions = crafty_permissions.join("");
|
||||||
|
console.log(server_permissions);
|
||||||
|
console.log(crafty_permissions);
|
||||||
|
console.log(formDataObject);
|
||||||
|
// Format the plain form data as JSON
|
||||||
|
let formDataJsonString = JSON.stringify({
|
||||||
|
"name": formDataObject.name,
|
||||||
|
"server_permissions_mask": server_permissions,
|
||||||
|
"crafty_permissions_mask": crafty_permissions,
|
||||||
|
"superuser": $("#superuser").prop('checked'),
|
||||||
|
});
|
||||||
|
console.log(formDataJsonString);
|
||||||
|
|
||||||
|
let res = await fetch(`/api/v2/users/${userId}/key/`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
body: formDataJsonString,
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
console.log("ready!");
|
console.log("ready!");
|
||||||
$('.delete-api-key').click(function () {
|
$('.delete-api-key').click(async function () {
|
||||||
var keyId = $(this).data("key-id");
|
let keyId = $(this).data("key-id");
|
||||||
var keyName = $(this).data("key-name");
|
let token = getCookie("_xsrf");
|
||||||
bootbox.confirm({
|
let res = await fetch(`/api/v2/users/${userId}/key/${keyId}`, {
|
||||||
title: `Remove API key ${keyName}?`,
|
method: 'DELETE',
|
||||||
message: "Do you want to delete this API key? This cannot be undone.",
|
headers: {
|
||||||
buttons: {
|
'X-XSRFToken': token
|
||||||
cancel: {
|
|
||||||
label: '<i class="fas fa-times"></i> {{ translate("panelConfig", "cancel", data['lang']) }}'
|
|
||||||
},
|
|
||||||
confirm: {
|
|
||||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback: function (result) {
|
|
||||||
if (result) {
|
|
||||||
var token = getCookie("_xsrf")
|
|
||||||
$.ajax({
|
|
||||||
type: "DELETE",
|
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: '/panel/remove_apikey?id=' + keyId,
|
|
||||||
success: function (data) {
|
|
||||||
location.reload();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
$('.get-a-token').click(function () {
|
|
||||||
var keyId = $(this).data("key-id");
|
|
||||||
var keyName = $(this).data("key-name");
|
|
||||||
var token = getCookie("_xsrf")
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: '/panel/get_token?id=' + keyId,
|
|
||||||
success: function (data) {
|
|
||||||
bootbox.alert({
|
|
||||||
title: `API token for ${keyName}`,
|
|
||||||
message: `Here is an API token for ${keyName}:\n<pre style="white-space: pre-wrap;color:white;word-break:break-all;background: grey;border-radius: 5px;">${data}</pre>`
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
let responseData = await res.json();
|
||||||
});
|
if (responseData.status === "ok") {
|
||||||
|
location.reload()
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$('.get-a-token').click(async function () {
|
||||||
|
let keyId = $(this).data("key-id");
|
||||||
|
let keyName = $(this).data("key-name");
|
||||||
|
let token = getCookie("_xsrf");
|
||||||
|
let res = await fetch(`/api/v2/users/${userId}/key/${keyId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
bootbox.alert({
|
||||||
|
title: `API token for ${keyName}`,
|
||||||
|
message: `Here is an API token for ${keyName}:\n<pre style="white-space: pre-wrap;color:white;word-break:break-all;background: grey;border-radius: 5px;">${responseData.data}</pre>`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
async function send_command_to_server(command) {
|
async function send_command_to_server(command) {
|
||||||
console.log(command)
|
console.log(command)
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
console.log('sending command: ' + command)
|
console.log('sending command: ' + command)
|
||||||
|
|
||||||
let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
|
let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
|
||||||
|
@ -44,12 +44,7 @@
|
|||||||
<div class="col-md-6 col-sm-12">
|
<div class="col-md-6 col-sm-12">
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<form class="forms-sample" method="post" action="/panel/server_backup">
|
<form id="backup-form" class="forms-sample">
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
|
||||||
<input type="hidden" name="subpage" value="backup">
|
|
||||||
|
|
||||||
|
|
||||||
{% if data['backing_up'] %}
|
{% if data['backing_up'] %}
|
||||||
<div class="progress" style="height: 15px;">
|
<div class="progress" style="height: 15px;">
|
||||||
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
|
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
|
||||||
@ -149,8 +144,6 @@
|
|||||||
data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{
|
data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{
|
||||||
translate('serverBackups', 'clickExclude', data['lang']) }}</button>
|
translate('serverBackups', 'clickExclude', data['lang']) }}</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" class="form-control" name="changed" id="changed" value="0"
|
|
||||||
style="visibility: hidden;"></input>
|
|
||||||
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
|
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
@ -175,10 +168,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{
|
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal"><i class="fa-solid fa-xmark"></i></button>
|
||||||
translate('serverBackups', 'cancel', data['lang']) }}</button>
|
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary"><i class="fa-solid fa-thumbs-up"></i></button>
|
||||||
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
|
|
||||||
translate('serverWizard', 'save', data['lang']) }}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -316,66 +307,73 @@
|
|||||||
return r ? r[1] : undefined;
|
return r ? r[1] : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function backup_started() {
|
async function backup_started() {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
document.getElementById('backup_button').style.visibility = 'hidden';
|
document.getElementById('backup_button').style.visibility = 'hidden';
|
||||||
var dialog = bootbox.dialog({
|
var dialog = bootbox.dialog({
|
||||||
message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}",
|
message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}",
|
||||||
closeButton: false
|
closeButton: false
|
||||||
});
|
});
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/servers/${server_id}/action/backup_server`, {
|
||||||
type: "POST",
|
method: 'POST',
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: {
|
||||||
url: `/api/v2/servers/${server_id}/action/backup_server`,
|
'X-XSRFToken': token
|
||||||
success: function (data) {
|
}
|
||||||
return;
|
});
|
||||||
},
|
let responseData = await res.json();
|
||||||
});
|
if (responseData.status === "ok") {
|
||||||
|
console.log(responseData);
|
||||||
|
process_tree_response(responseData);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
async function del_backup(filename, id) {
|
||||||
function del_backup(filename, id) {
|
const token = getCookie("_xsrf")
|
||||||
var token = getCookie("_xsrf")
|
let contents = JSON.stringify({"filename": filename})
|
||||||
|
let res = await fetch(`/api/v2/servers/${id}/backups/backup/`, {
|
||||||
data_to_send = { file_name: filename }
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
console.log('Sending Command to delete backup: ' + filename)
|
'token': token,
|
||||||
$.ajax({
|
|
||||||
type: "DELETE",
|
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: '/ajax/del_backup?server_id=' + id,
|
|
||||||
data: {
|
|
||||||
file_path: filename,
|
|
||||||
id: id
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
location.reload();
|
|
||||||
},
|
},
|
||||||
|
body: contents
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.reload();
|
||||||
|
}else{
|
||||||
|
bootbox.alert({"title": responseData.status,
|
||||||
|
"message": responseData.error})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function restore_backup(filename, id) {
|
async function restore_backup(filename, id) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
|
let contents = JSON.stringify({"filename": filename})
|
||||||
var dialog = bootbox.dialog({
|
var dialog = bootbox.dialog({
|
||||||
message: "<i class='fa fa-spin fa-spinner'></i> {{ translate('serverBackups', 'restoring', data['lang']) }}",
|
message: "<i class='fa fa-spin fa-spinner'></i> {{ translate('serverBackups', 'restoring', data['lang']) }}",
|
||||||
closeButton: false
|
closeButton: false
|
||||||
});
|
});
|
||||||
|
let res = await fetch(`/api/v2/servers/${id}/backups/backup/`, {
|
||||||
console.log('Sending Command to restore backup: ' + filename)
|
method: 'POST',
|
||||||
$.ajax({
|
headers: {
|
||||||
type: "POST",
|
'token': token,
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: '/ajax/restore_backup?server_id=' + id,
|
|
||||||
data: {
|
|
||||||
zip_file: filename,
|
|
||||||
id: id
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
setTimeout(function () {
|
|
||||||
location.href = ('/panel/dashboard');
|
|
||||||
}, 15000);
|
|
||||||
},
|
},
|
||||||
|
body: contents
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.href = "/panel/dashboard";
|
||||||
|
}else{
|
||||||
|
bootbox.alert({"title": responseData.status,
|
||||||
|
"message": responseData.error})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#before-check").on("click", function () {
|
$("#before-check").on("click", function () {
|
||||||
@ -395,7 +393,66 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function replacer(key, value) {
|
||||||
|
if (key != "backup_before" && key != "backup_after") {
|
||||||
|
if (typeof value == "boolean" || key === "executable_update_url") {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return (isNaN(value) ? value : +value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
$("#backup-form").on("submit", async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const token = getCookie("_xsrf")
|
||||||
|
let backupForm = document.getElementById("backup-form");
|
||||||
|
|
||||||
|
let formData = new FormData(backupForm);
|
||||||
|
//Remove checks that we don't need in form data.
|
||||||
|
formData.delete("after-check");
|
||||||
|
formData.delete("before-check");
|
||||||
|
//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.compress = $("#compress").prop('checked');
|
||||||
|
formDataObject.shutdown = $("#shutdown").prop('checked');
|
||||||
|
let excluded = [];
|
||||||
|
$('input.excluded:checkbox:checked').each(function () {
|
||||||
|
excluded.push($(this).val());
|
||||||
|
});
|
||||||
|
if ($("#root_files_button").hasClass("clicked")){
|
||||||
|
formDataObject.exclusions = excluded;
|
||||||
|
}
|
||||||
|
console.log(excluded);
|
||||||
|
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/${server_id}/backups/`, {
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($('#backup_path').val() == '') {
|
if ($('#backup_path').val() == '') {
|
||||||
console.log('true')
|
console.log('true')
|
||||||
@ -457,7 +514,7 @@
|
|||||||
console.log(result);
|
console.log(result);
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
var full_path = backup_path + '/' + file_to_del;
|
var full_path = backup_path + '/' + file_to_del;
|
||||||
del_backup(full_path, server_id);
|
del_backup(file_to_del, server_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -505,27 +562,15 @@
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('root_files_button').classList.add('clicked');
|
document.getElementById('root_files_button').classList.add('clicked');
|
||||||
document.getElementById("changed").value = 1;
|
|
||||||
}
|
}
|
||||||
path = $("#root_files_button").data('server_path')
|
path = $("#root_files_button").data('server_path')
|
||||||
console.log($("#root_files_button").data('server_path'))
|
console.log($("#root_files_button").data('server_path'))
|
||||||
var token = getCookie("_xsrf");
|
const token = getCookie("_xsrf");
|
||||||
var dialog = bootbox.dialog({
|
var dialog = bootbox.dialog({
|
||||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||||
closeButton: false
|
closeButton: false
|
||||||
});
|
});
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: '/ajax/backup_select?id=' + server_id + '&path=' + path,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bootbox.alert("You must input a path before selecting this button");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (webSocket) {
|
|
||||||
webSocket.on('send_temp_path', function (data) {
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
var x = document.querySelector('.bootbox');
|
var x = document.querySelector('.bootbox');
|
||||||
if (x) {
|
if (x) {
|
||||||
@ -535,13 +580,15 @@
|
|||||||
if (x) {
|
if (x) {
|
||||||
x.remove()
|
x.remove()
|
||||||
}
|
}
|
||||||
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
document.getElementById('main-tree-input').setAttribute('value', path)
|
||||||
getTreeView(data.path);
|
getTreeView(path);
|
||||||
show_file_tree();
|
show_file_tree();
|
||||||
|
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
} else {
|
||||||
}
|
bootbox.alert("You must input a path before selecting this button");
|
||||||
|
}
|
||||||
|
});
|
||||||
if (webSocket) {
|
if (webSocket) {
|
||||||
webSocket.on('backup_status', function (backup) {
|
webSocket.on('backup_status', function (backup) {
|
||||||
if (backup.percent >= 100) {
|
if (backup.percent >= 100) {
|
||||||
@ -558,68 +605,82 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTreeView(path) {
|
function getDirView(event){
|
||||||
path = path
|
let path = event.target.parentElement.getAttribute("data-path");
|
||||||
|
if (document.getElementById(path).classList.contains('clicked')) {
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
getTreeView(path);
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
}
|
||||||
type: "GET",
|
async function getTreeView(path){
|
||||||
url: '/ajax/get_backup_tree?id=' + server_id + '&path=' + path,
|
console.log(path)
|
||||||
dataType: 'text',
|
const token = getCookie("_xsrf");
|
||||||
success: function (data) {
|
let res = await fetch(`/api/v2/servers/${server_id}/files`, {
|
||||||
console.log("got response:");
|
method: 'POST',
|
||||||
console.log(data);
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
body: JSON.stringify({"page": "backups", "path": path}),
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
console.log(responseData);
|
||||||
|
process_tree_response(responseData);
|
||||||
|
|
||||||
dataArr = data.split('\n');
|
} else {
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
||||||
text = dataArr.join('\n');
|
|
||||||
|
|
||||||
try {
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function process_tree_response(response) {
|
||||||
|
let path = response.data.root_path.path;
|
||||||
|
let text = `<ul class="tree-nested d-block" id="${path}ul">`;
|
||||||
|
Object.entries(response.data).forEach(([key, value]) => {
|
||||||
|
if (key === "root_path" || key === "db_stats"){
|
||||||
|
//continue is not valid in for each. Return acts as a continue.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let checked = ""
|
||||||
|
let dpath = value.path;
|
||||||
|
let filename = key;
|
||||||
|
if (value.dir){
|
||||||
|
if (value.excluded){
|
||||||
|
checked = "checked"
|
||||||
|
}
|
||||||
|
text += `<li class="tree-item" data-path="${dpath}">
|
||||||
|
\n<div id="${dpath}" data-path="${dpath}" data-name="${filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||||
|
<input type="checkbox" class="checkBoxClass excluded" value="${dpath}" ${checked}>
|
||||||
|
<span id="${dpath}span" class="files-tree-title" data-path="${dpath}" data-name="${filename}" onclick="getDirView(event)">
|
||||||
|
<i style="color: var(--info);" class="far fa-folder"></i>
|
||||||
|
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
||||||
|
<strong>${filename}</strong>
|
||||||
|
</span>
|
||||||
|
</input></div><li>`
|
||||||
|
}else{
|
||||||
|
text += `<li
|
||||||
|
class="d-block tree-ctx-item tree-file"
|
||||||
|
data-path="${dpath}"
|
||||||
|
data-name="${filename}"
|
||||||
|
onclick=""><input type='checkbox' class="checkBoxClass excluded" name='root_path' value="${dpath}" ${checked}><span style="margin-right: 6px;">
|
||||||
|
<i class="far fa-file"></i></span></input>${filename}</li>`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
text += `</ul>`;
|
||||||
|
if(response.data.root_path.top){
|
||||||
|
try {
|
||||||
document.getElementById('main-tree-div').innerHTML += text;
|
document.getElementById('main-tree-div').innerHTML += text;
|
||||||
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||||
} catch {
|
} catch {
|
||||||
document.getElementById('files-tree').innerHTML = text;
|
document.getElementById('files-tree').innerHTML = text;
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
try {
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function getToggleMain(event) {
|
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
document.getElementById("files-tree").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getDirView(event) {
|
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
|
|
||||||
if (document.getElementById(path).classList.contains('clicked')) {
|
|
||||||
|
|
||||||
var toggler = document.getElementById(path + "span");
|
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
|
||||||
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: '/ajax/get_backup_dir?id=' + server_id + '&path=' + path,
|
|
||||||
dataType: 'text',
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
|
|
||||||
dataArr = data.split('\n');
|
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
||||||
text = dataArr.join('\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
document.getElementById(path + "span").classList.add('tree-caret-down');
|
document.getElementById(path + "span").classList.add('tree-caret-down');
|
||||||
document.getElementById(path).innerHTML += text;
|
document.getElementById(path).innerHTML += text;
|
||||||
document.getElementById(path).classList.add("clicked");
|
document.getElementById(path).classList.add("clicked");
|
||||||
@ -627,7 +688,7 @@
|
|||||||
console.log("Bad")
|
console.log("Bad")
|
||||||
}
|
}
|
||||||
|
|
||||||
var toggler = document.getElementById(path);
|
var toggler = document.getElementById(path + "span");
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
if (toggler.classList.contains('files-tree-title')) {
|
||||||
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
||||||
@ -635,10 +696,15 @@
|
|||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getToggleMain(event) {
|
||||||
|
path = event.target.parentElement.getAttribute('data-path');
|
||||||
|
document.getElementById("files-tree").classList.toggle("d-block");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret");
|
||||||
|
}
|
||||||
function show_file_tree() {
|
function show_file_tree() {
|
||||||
$("#dir_select").modal();
|
$("#dir_select").modal();
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function deleteServerE(callback) {
|
function deleteServerE(callback) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: { 'X-XSRFToken': token },
|
||||||
@ -318,7 +318,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function deleteServerFilesE(path, callback) {
|
function deleteServerFilesE(path, callback) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: { 'X-XSRFToken': token },
|
||||||
@ -334,7 +334,7 @@
|
|||||||
|
|
||||||
function send_command(serverId, command) {
|
function send_command(serverId, command) {
|
||||||
//<!-- this getCookie function is in base.html-->
|
//<!-- this getCookie function is in base.html-->
|
||||||
var token = getCookie("_xsrf");
|
const token = getCookie("_xsrf");
|
||||||
if (command == "update_executable") {
|
if (command == "update_executable") {
|
||||||
document.getElementById("update-spinner").style.visibility = "visible";
|
document.getElementById("update-spinner").style.visibility = "visible";
|
||||||
}
|
}
|
||||||
@ -460,7 +460,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
setTimeout(function () { window.location = '/panel/dashboard'; }, 5000);
|
setTimeout(function () { window.location = '/panel/dashboard'; }, 5000);
|
||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
@ -549,7 +549,7 @@
|
|||||||
});
|
});
|
||||||
$("#config_form").on("submit", async function (e) {
|
$("#config_form").on("submit", async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
let configForm = document.getElementById("config_form");
|
let configForm = document.getElementById("config_form");
|
||||||
|
|
||||||
let formData = new FormData(configForm);
|
let formData = new FormData(configForm);
|
||||||
@ -576,7 +576,7 @@
|
|||||||
});
|
});
|
||||||
let responseData = await res.json();
|
let responseData = await res.json();
|
||||||
if (responseData.status === "ok") {
|
if (responseData.status === "ok") {
|
||||||
window.location.reload();
|
location.reload(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bootbox.alert({
|
bootbox.alert({
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
translate('serverFiles', 'download', data['lang']) }}</a>
|
translate('serverFiles', 'download', data['lang']) }}</a>
|
||||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#"
|
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#"
|
||||||
style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||||
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#" style="color: red">{{
|
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteDir" href="#" style="color: red">{{
|
||||||
translate('serverFiles', 'delete', data['lang']) }}</a>
|
translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||||
<a href="javascript:void(0)" class="closebtn" style="color: var(--info);"
|
<a href="javascript:void(0)" class="closebtn" style="color: var(--info);"
|
||||||
onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{
|
onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{
|
||||||
@ -398,32 +398,36 @@
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let filePath = '', serverFileContent = '';
|
let path = '', serverFileContent = '';
|
||||||
|
|
||||||
function clickOnFile(event) {
|
async function clickOnFile(event) {
|
||||||
filePath = event.target.getAttribute('data-path');
|
const token = getCookie("_xsrf");
|
||||||
$.ajax({
|
path = event.target.getAttribute('data-path');
|
||||||
type: 'GET',
|
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
|
||||||
url: "/files/get_file?id=" + serverId + "&file_path=" + encodeURIComponent(filePath),
|
method: 'POST',
|
||||||
dataType: 'text',
|
headers: {
|
||||||
success: function (data) {
|
'X-XSRFToken': token
|
||||||
console.log('Got File Contents From Server');
|
|
||||||
json = JSON.parse(data)
|
|
||||||
if (json.error) {
|
|
||||||
$('#editorParent').toggle(false) // hide
|
|
||||||
$('#fileError').toggle(true) // show
|
|
||||||
$('#fileError').text("{{ translate('serverFiles', 'fileReadError', data['lang']) }}: " + json.error) // show error
|
|
||||||
editor.blur()
|
|
||||||
} else {
|
|
||||||
$('#editorParent').toggle(true) // show
|
|
||||||
$('#fileError').toggle(false) // hide
|
|
||||||
setFileName(event.target.innerText);
|
|
||||||
editor.session.setValue(json.content);
|
|
||||||
serverFileContent = json.content;
|
|
||||||
setSaveStatus(true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "page": "files", "path": path }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
console.log(responseData)
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
console.log('Got File Contents From Server');
|
||||||
|
$('#editorParent').toggle(true) // show
|
||||||
|
$('#fileError').toggle(false) // hide
|
||||||
|
setFileName(event.target.innerText);
|
||||||
|
editor.session.setValue(responseData.data);
|
||||||
|
serverFileContent = responseData.data;
|
||||||
|
setSaveStatus(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFileName(name) {
|
function setFileName(name) {
|
||||||
@ -577,124 +581,141 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function save() {
|
||||||
function save() {
|
|
||||||
let text = editor.session.getValue();
|
let text = editor.session.getValue();
|
||||||
|
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
|
||||||
type: "PUT",
|
method: 'PATCH',
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: {
|
||||||
url: "/files/save_file?id=" + serverId,
|
'X-XSRFToken': token
|
||||||
data: {
|
|
||||||
file_contents: text,
|
|
||||||
file_path: filePath
|
|
||||||
},
|
},
|
||||||
success: (data) => {
|
body: JSON.stringify({ "path": path, "contents": text }),
|
||||||
serverFileContent = text;
|
|
||||||
setSaveStatus(true)
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
serverFileContent = text;
|
||||||
|
setSaveStatus(true)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFile(parent, name, callback) {
|
async function createFile(parent, name, callback) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
|
||||||
type: "POST",
|
method: 'PUT',
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: {
|
||||||
url: "/files/create_file?id=" + serverId,
|
'X-XSRFToken': token
|
||||||
data: {
|
|
||||||
file_parent: parent,
|
|
||||||
file_name: name
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
callback();
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "parent": parent, "name": name, "directory": false }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
getTreeView($('#root_dir').data('path'));
|
||||||
|
setTreeViewContext();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDir(parent, name, callback) {
|
|
||||||
var token = getCookie("_xsrf")
|
async function createDir(parent, name, callback) {
|
||||||
$.ajax({
|
const token = getCookie("_xsrf")
|
||||||
type: "POST",
|
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
|
||||||
headers: { 'X-XSRFToken': token },
|
method: 'PUT',
|
||||||
url: "/files/create_dir?id=" + serverId,
|
headers: {
|
||||||
data: {
|
'X-XSRFToken': token
|
||||||
dir_parent: parent,
|
|
||||||
dir_name: name
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
callback();
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "parent": parent, "name": name, "directory": true }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
getTreeView($('#root_dir').data('path'));
|
||||||
|
setTreeViewContext();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renameItem(path, name, callback) {
|
async function renameItem(path, name, callback) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
|
||||||
type: "PATCH",
|
method: 'PATCH',
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: {
|
||||||
url: "/files/rename_file?id=" + serverId,
|
'X-XSRFToken': token
|
||||||
data: {
|
|
||||||
item_path: path,
|
|
||||||
new_item_name: name
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
callback();
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "path": path, "new_name": name }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
getTreeView($('#root_dir').data('path'));
|
||||||
|
setTreeViewContext();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteFile(path, callback) {
|
async function deleteItem(path, el, callback) {
|
||||||
console.log('Deleting: ' + path)
|
const token = getCookie("_xsrf");
|
||||||
var token = getCookie("_xsrf")
|
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
|
||||||
$.ajax({
|
method: 'DELETE',
|
||||||
type: "DELETE",
|
headers: {
|
||||||
headers: { 'X-XSRFToken': token },
|
'X-XSRFToken': token
|
||||||
url: "/files/del_file?id=" + serverId,
|
|
||||||
data: {
|
|
||||||
file_path: path
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
callback();
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "filename": path }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
el = document.getElementById(path + "li");
|
||||||
|
$(el).remove();
|
||||||
|
document.getElementById('files-tree-nav').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteDir(path, callback) {
|
async function unZip(path, callback) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/servers/${serverId}/files/zip/`, {
|
||||||
type: "DELETE",
|
method: 'POST',
|
||||||
headers: { 'X-XSRFToken': token },
|
headers: {
|
||||||
url: "/files/del_dir?id=" + serverId,
|
'X-XSRFToken': token
|
||||||
data: {
|
|
||||||
dir_path: path
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
callback();
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "folder": path }),
|
||||||
});
|
});
|
||||||
}
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
getTreeView($('#root_dir').data('path'));
|
||||||
|
setTreeViewContext();
|
||||||
|
} else {
|
||||||
|
|
||||||
function unZip(path, callback) {
|
bootbox.alert({
|
||||||
console.log('path: ', path)
|
title: responseData.status,
|
||||||
var token = getCookie("_xsrf")
|
message: responseData.error
|
||||||
$.ajax({
|
});
|
||||||
type: "POST",
|
}
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: "/files/unzip_file?id=" + serverId,
|
|
||||||
data: {
|
|
||||||
path: path
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
window.location.href = "/panel/server_detail?id=" + serverId + "&subpage=files";
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendFile(file, path, serverId, left, i, onProgress) {
|
async function sendFile(file, path, serverId, left, i, onProgress) {
|
||||||
@ -882,36 +903,104 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTreeView(event) {
|
function getDirView(event) {
|
||||||
const path = $('#root_dir').data('path');;
|
let path = event.target.parentElement.getAttribute("data-path");
|
||||||
|
if (document.getElementById(path).classList.contains('clicked')) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
getTreeView(path);
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
}
|
||||||
type: "GET",
|
async function getTreeView(path) {
|
||||||
url: "/files/get_tree?id=" + serverId + "&path=" + path,
|
const token = getCookie("_xsrf");
|
||||||
dataType: 'text',
|
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
|
||||||
success: function (data) {
|
method: 'POST',
|
||||||
console.log("got response:");
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
dataArr = data.split('\n');
|
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
||||||
text = dataArr.join('\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
document.getElementById(path).innerHTML += text;
|
|
||||||
event.target.parentElement.classList.add("clicked");
|
|
||||||
} catch {
|
|
||||||
document.getElementById('files-tree').innerHTML = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
|
||||||
|
|
||||||
setTimeout(function () { setTreeViewContext() }, 1000);
|
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ "page": "files", "path": path }),
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
console.log(responseData);
|
||||||
|
process_tree_response(responseData);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function process_tree_response(response) {
|
||||||
|
let path = response.data.root_path.path;
|
||||||
|
let text = ``;
|
||||||
|
if (!response.data.root_path.top) {
|
||||||
|
text = `<ul class="tree-nested d-block" id="${path}ul">`;
|
||||||
|
}
|
||||||
|
Object.entries(response.data).forEach(([key, value]) => {
|
||||||
|
if (key === "root_path" || key === "db_stats") {
|
||||||
|
//continue is not valid in for each. Return acts as a continue.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let checked = ""
|
||||||
|
let dpath = value.path;
|
||||||
|
let filename = key;
|
||||||
|
if (value.dir) {
|
||||||
|
if (value.excluded) {
|
||||||
|
checked = "checked"
|
||||||
|
}
|
||||||
|
text += `<li class="tree-item" id="${dpath}li" data-path="${dpath}">
|
||||||
|
\n<div id="${dpath}" data-path="${dpath}" data-name="${filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||||
|
<input type="checkbox" class="checkBoxClass d-none file-check" name="root_path" value="${dpath}" ${checked}>
|
||||||
|
<span id="${dpath}span" class="files-tree-title" data-path="${dpath}" data-name="${filename}" onclick="getDirView(event)">
|
||||||
|
<i style="color: var(--info);" class="far fa-folder"></i>
|
||||||
|
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
||||||
|
${filename}
|
||||||
|
</span>
|
||||||
|
</input></div></li>`
|
||||||
|
} else {
|
||||||
|
text += `<li
|
||||||
|
class="d-block tree-ctx-item tree-file"
|
||||||
|
data-path="${dpath}"
|
||||||
|
data-name="${filename}"
|
||||||
|
onclick="clickOnFile(event)" id="${dpath}li"><input type='checkbox' class="checkBoxClass d-none file-check" name='root_path' value="${dpath}" ${checked}><span style="margin-right: 6px;">
|
||||||
|
<i class="far fa-file"></i></span></input>${filename}</li>`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response.data.root_path.top) {
|
||||||
|
text += `</ul>`;
|
||||||
|
}
|
||||||
|
if (response.data.root_path.top) {
|
||||||
|
try {
|
||||||
|
document.getElementById('main-tree-div').innerHTML += text;
|
||||||
|
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||||
|
} catch {
|
||||||
|
document.getElementById('files-tree').innerHTML = text;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
document.getElementById(path + "span").classList.add('tree-caret-down');
|
||||||
|
document.getElementById(path).innerHTML += text;
|
||||||
|
document.getElementById(path).classList.add("clicked");
|
||||||
|
} catch {
|
||||||
|
console.log("Bad")
|
||||||
|
}
|
||||||
|
|
||||||
|
var toggler = document.getElementById(path + "span");
|
||||||
|
|
||||||
|
if (toggler.classList.contains('files-tree-title')) {
|
||||||
|
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
||||||
|
document.getElementById(path + "ul").classList.toggle("d-block");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(function () { setTreeViewContext() }, 1000);
|
||||||
|
}
|
||||||
function getToggleMain(event) {
|
function getToggleMain(event) {
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
path = event.target.parentElement.getAttribute('data-path');
|
||||||
document.getElementById("files-tree").classList.toggle("d-block");
|
document.getElementById("files-tree").classList.toggle("d-block");
|
||||||
@ -919,53 +1008,6 @@
|
|||||||
document.getElementById(path + "span").classList.toggle("tree-caret");
|
document.getElementById(path + "span").classList.toggle("tree-caret");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDirView(event) {
|
|
||||||
let path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
|
|
||||||
if (document.getElementById(path).classList.contains('clicked')) {
|
|
||||||
|
|
||||||
var toggler = document.getElementById(path + "span");
|
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
|
||||||
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: "/files/get_dir?id=" + serverId + "&path=" + path,
|
|
||||||
dataType: 'text',
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
|
|
||||||
dataArr = data.split('\n');
|
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
||||||
text = dataArr.join('\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
document.getElementById(path + "span").classList.add('tree-caret-down');
|
|
||||||
document.getElementById(path).innerHTML += text;
|
|
||||||
document.getElementById(path).classList.add("clicked");
|
|
||||||
} catch {
|
|
||||||
console.log("Bad")
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function () { setTreeViewContext() }, 1000);
|
|
||||||
|
|
||||||
var toggler = document.getElementById(path);
|
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
|
||||||
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
|
||||||
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTreeViewContext() {
|
function setTreeViewContext() {
|
||||||
var treeItems = Array.from(document.getElementsByClassName('tree-ctx-item'));
|
var treeItems = Array.from(document.getElementsByClassName('tree-ctx-item'));
|
||||||
|
|
||||||
@ -1134,45 +1176,12 @@
|
|||||||
},
|
},
|
||||||
callback: function (result) {
|
callback: function (result) {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
deleteFile(path, function () {
|
deleteItem(path);
|
||||||
el = document.getElementById(path + "li");
|
|
||||||
$(el).remove();
|
|
||||||
document.getElementById('files-tree-nav').style.display = 'none';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteDirE(event) {
|
getTreeView($('#root_dir').data('path'));
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
name = event.target.parentElement.getAttribute('data-name');
|
|
||||||
bootbox.confirm({
|
|
||||||
size: "",
|
|
||||||
title: "{% raw translate('serverFiles', 'deleteItemQuestion', data['lang']) %}",
|
|
||||||
closeButton: false,
|
|
||||||
message: "{% raw translate('serverFiles', 'deleteItemQuestionMessage', data['lang']) %}",
|
|
||||||
buttons: {
|
|
||||||
confirm: {
|
|
||||||
label: "{{ translate('serverFiles', 'yesDelete', data['lang']) }}",
|
|
||||||
className: 'btn-danger'
|
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
label: "{{ translate('serverFiles', 'noDelete', data['lang']) }}",
|
|
||||||
className: 'btn-link'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback: function (result) {
|
|
||||||
if (!result) return;
|
|
||||||
deleteDir(path, function () {
|
|
||||||
el = document.getElementById(path + "li");
|
|
||||||
$(el).remove();
|
|
||||||
document.getElementById('files-tree-nav').style.display = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getTreeView();
|
|
||||||
setTreeViewContext();
|
setTreeViewContext();
|
||||||
|
|
||||||
function setKeyboard(target) {
|
function setKeyboard(target) {
|
||||||
|
@ -77,8 +77,8 @@
|
|||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
<script>
|
||||||
// ##### Log Filter Block #####
|
// ##### Log Filter Block #####
|
||||||
var lines = [];
|
let lines = [];
|
||||||
var words = [];
|
let words = [];
|
||||||
if (localStorage.getItem("words")) {
|
if (localStorage.getItem("words")) {
|
||||||
try {
|
try {
|
||||||
words = JSON.parse(localStorage.getItem("words"));
|
words = JSON.parse(localStorage.getItem("words"));
|
||||||
@ -188,27 +188,40 @@
|
|||||||
|
|
||||||
// Populate logs and filter if present
|
// Populate logs and filter if present
|
||||||
const serverId = new URLSearchParams(document.location.search).get('id')
|
const serverId = new URLSearchParams(document.location.search).get('id')
|
||||||
function get_server_log() {
|
async function get_server_log() {
|
||||||
|
const token = getCookie("_xsrf")
|
||||||
|
let colors = true;
|
||||||
if (!$("#stop_scroll").is(':checked')) {
|
if (!$("#stop_scroll").is(':checked')) {
|
||||||
$.ajax({
|
let res = await fetch(`/api/v2/servers/${serverId}/logs?colors=${colors}`, {
|
||||||
type: 'GET',
|
method: 'GET',
|
||||||
url: '/ajax/server_log?id=' + serverId + '&full=1',
|
headers: {
|
||||||
dataType: 'text',
|
'X-XSRFToken': token
|
||||||
success: function (data) {
|
|
||||||
console.log('Got Log From Server')
|
|
||||||
$('#virt_console').html(data);
|
|
||||||
scroll();
|
|
||||||
lines = document.querySelectorAll('.box');
|
|
||||||
hideFilteredWords();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
let html = ``
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
for (let value of responseData.data) {
|
||||||
|
html += `<span class='box'>${value}<br /></span>`
|
||||||
|
}
|
||||||
|
console.log('Got Log From Server')
|
||||||
|
$('#virt_console').html(html);
|
||||||
|
scroll();
|
||||||
|
lines = document.querySelectorAll('.box');
|
||||||
|
hideFilteredWords();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
console.log("ready!");
|
console.log("ready!");
|
||||||
get_server_log();
|
get_server_log();
|
||||||
populateWords();
|
populateWords();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% end %}
|
{% end %}
|
@ -245,14 +245,12 @@
|
|||||||
}else {
|
}else {
|
||||||
return (isNaN(value) ? value : +value);
|
return (isNaN(value) ? value : +value);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (value === "" && key == "start_time"){
|
||||||
if (value === "" && key == "start_time"){
|
|
||||||
return "00:00";
|
return "00:00";
|
||||||
}else{
|
}else{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const serverId = new URLSearchParams(document.location.search).get('id');
|
const serverId = new URLSearchParams(document.location.search).get('id');
|
||||||
const schId = new URLSearchParams(document.location.search).get('sch_id');
|
const schId = new URLSearchParams(document.location.search).get('sch_id');
|
||||||
@ -260,7 +258,7 @@
|
|||||||
console.log("ready!");
|
console.log("ready!");
|
||||||
$("#new_schedule_form").on("submit", async function (e) {
|
$("#new_schedule_form").on("submit", async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
let schForm = document.getElementById("new_schedule_form");
|
let schForm = document.getElementById("new_schedule_form");
|
||||||
|
|
||||||
let formData = new FormData(schForm);
|
let formData = new FormData(schForm);
|
||||||
@ -305,7 +303,7 @@
|
|||||||
|
|
||||||
$("#schedule_form").on("submit", async function (e) {
|
$("#schedule_form").on("submit", async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
let schForm = document.getElementById("schedule_form");
|
let schForm = document.getElementById("schedule_form");
|
||||||
|
|
||||||
let formData = new FormData(schForm);
|
let formData = new FormData(schForm);
|
||||||
|
@ -47,14 +47,20 @@
|
|||||||
<h4 class="card-title"><i class="fas fa-calendar"></i> {{ translate('serverSchedules',
|
<h4 class="card-title"><i class="fas fa-calendar"></i> {{ translate('serverSchedules',
|
||||||
'scheduledTasks', data['lang']) }} </h4>
|
'scheduledTasks', data['lang']) }} </h4>
|
||||||
{% if data['user_data']['hints'] %}
|
{% if data['user_data']['hints'] %}
|
||||||
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" , data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" , data-placement="bottom"></span>
|
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" ,
|
||||||
|
data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" ,
|
||||||
|
data-placement="bottom"></span>
|
||||||
{% end %}
|
{% end %}
|
||||||
<div>
|
<div>
|
||||||
<button onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`" class="btn btn-info">{{ translate('serverSchedules', 'create', data['lang']) }}<i class="fas fa-pencil-alt"></i></button>
|
<button
|
||||||
|
onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`"
|
||||||
|
class="btn btn-info">{{ translate('serverSchedules', 'create', data['lang']) }}<i
|
||||||
|
class="fas fa-pencil-alt"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" style="table-layout:fixed;">
|
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table"
|
||||||
|
style="table-layout:fixed;" aria-describedby="Schedule List">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="rounded">
|
<tr class="rounded">
|
||||||
<th style="width: 2%; min-width: 10px;">{{ translate('serverSchedules', 'name', data['lang']) }}
|
<th style="width: 2%; min-width: 10px;">{{ translate('serverSchedules', 'name', data['lang']) }}
|
||||||
@ -101,10 +107,14 @@
|
|||||||
<p>{{schedule.next_run}}</p>
|
<p>{{schedule.next_run}}</p>
|
||||||
</td>
|
</td>
|
||||||
<td id="{{schedule.enabled}}" class="action">
|
<td id="{{schedule.enabled}}" class="action">
|
||||||
<input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
|
<input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle"
|
||||||
|
data-schedule-id="{{schedule.schedule_id}}"
|
||||||
|
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
|
||||||
</td>
|
</td>
|
||||||
<td id="{{schedule.action}}" class="action">
|
<td id="{{schedule.action}}" class="action">
|
||||||
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
|
<button
|
||||||
|
onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'"
|
||||||
|
class="btn btn-info">
|
||||||
<i class="fas fa-pencil-alt"></i>
|
<i class="fas fa-pencil-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
<br>
|
<br>
|
||||||
@ -118,7 +128,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<hr />
|
<hr />
|
||||||
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" style="table-layout:fixed;">
|
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table"
|
||||||
|
style="table-layout:fixed;" aria-describedby="Schedule List Mobile">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="rounded">
|
<tr class="rounded">
|
||||||
<th style="width: 25%; min-width: 50px;">{{ translate('serverSchedules', 'action', data['lang'])
|
<th style="width: 25%; min-width: 50px;">{{ translate('serverSchedules', 'action', data['lang'])
|
||||||
@ -151,7 +162,8 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog"
|
||||||
|
aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -198,14 +210,19 @@
|
|||||||
<p>zzzzzzz</p>
|
<p>zzzzzzz</p>
|
||||||
{% end %}
|
{% end %}
|
||||||
</li>
|
</li>
|
||||||
<li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
|
<li id="{{schedule.enabled}}" class="action"
|
||||||
|
style="border-top: .1em solid gray; border-bottom: .1em solid gray">
|
||||||
<h4>{{ translate('serverSchedules', 'enabled', data['lang']) }}</h4>
|
<h4>{{ translate('serverSchedules', 'enabled', data['lang']) }}</h4>
|
||||||
<input type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
|
<input type="checkbox" class="schedule-enabled-toggle"
|
||||||
|
data-schedule-id="{{schedule.schedule_id}}"
|
||||||
|
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
|
<button
|
||||||
|
onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'"
|
||||||
|
class="btn btn-info">
|
||||||
<i class="fas fa-pencil-alt"></i> {{ translate('serverSchedules', 'edit', data['lang'])
|
<i class="fas fa-pencil-alt"></i> {{ translate('serverSchedules', 'edit', data['lang'])
|
||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
@ -310,7 +327,7 @@
|
|||||||
const serverId = new URLSearchParams(document.location.search).get('id')
|
const serverId = new URLSearchParams(document.location.search).get('id')
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
console.log('ready for JS!')
|
console.log('ready for JS!');
|
||||||
$('#schedule_table').DataTable({
|
$('#schedule_table').DataTable({
|
||||||
'order': [4, 'asc'],
|
'order': [4, 'asc'],
|
||||||
}
|
}
|
||||||
@ -393,7 +410,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function del_task(sch_id, id) {
|
async function del_task(sch_id, id) {
|
||||||
var token = getCookie("_xsrf")
|
const token = getCookie("_xsrf")
|
||||||
|
|
||||||
let res = await fetch(`/api/v2/servers/${id}/tasks/${sch_id}`, {
|
let res = await fetch(`/api/v2/servers/${id}/tasks/${sch_id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
@ -46,15 +46,15 @@
|
|||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div id="virt_console" class=""
|
<div id="virt_console" class=""
|
||||||
style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid var(--outline); background-color:var(--card-banner-bg);height:500px; overflow: scroll;">
|
style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid var(--outline); background-color:var(--card-banner-bg);height:500px; overflow: scroll;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div style="gap: 0.5rem;" class="input-group flex-wrap">
|
<div style="gap: 0.5rem;" class="input-group flex-wrap">
|
||||||
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command"
|
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command"
|
||||||
name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}"
|
name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}"
|
||||||
autofocus="">
|
autofocus="">
|
||||||
<span class="input-group-btn ml-5">
|
<span class="input-group-btn ml-5">
|
||||||
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand',
|
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand',
|
||||||
data['lang']) }}</button>
|
data['lang']) }}</button>
|
||||||
@ -63,73 +63,74 @@
|
|||||||
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
||||||
{% if data['importing'] and data['server_stats']['running']%}
|
{% if data['importing'] and data['server_stats']['running']%}
|
||||||
<div id="update_control_buttons"
|
<div id="update_control_buttons"
|
||||||
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
style="visibility: visible">
|
style="visibility: visible">
|
||||||
<button onclick="" id="start-btn" style="max-width: 7rem;"
|
<button onclick="" id="start-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-warning m-1 flex-grow-1 disabled"><i
|
class="btn btn-warning m-1 flex-grow-1 disabled"><i
|
||||||
class="fa fa-spinner fa-spin"></i> {{translate('serverTerm', 'installing', data['lang']) }}</button>
|
class="fa fa-spinner fa-spin"></i> {{translate('serverTerm', 'installing', data['lang'])
|
||||||
|
}}</button>
|
||||||
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
||||||
data['lang']) %}</button>
|
data['lang']) %}</button>
|
||||||
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
{% elif data['server_stats']['updating']%}
|
{% elif data['server_stats']['updating']%}
|
||||||
<div id="update_control_buttons"
|
<div id="update_control_buttons"
|
||||||
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
style="visibility: visible">
|
style="visibility: visible">
|
||||||
<button onclick="" id="start-btn" style="max-width: 7rem;"
|
<button onclick="" id="start-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
|
class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
|
||||||
translate('serverTerm', 'updating', data['lang']) }}</button>
|
translate('serverTerm', 'updating', data['lang']) }}</button>
|
||||||
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
||||||
data['lang']) %}</button>
|
data['lang']) %}</button>
|
||||||
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
{% elif data['waiting_start'] %}
|
{% elif data['waiting_start'] %}
|
||||||
<div id="control_buttons"
|
<div id="control_buttons"
|
||||||
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
style="visibility: visible">
|
style="visibility: visible">
|
||||||
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;"
|
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;"
|
||||||
class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip"
|
class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip"
|
||||||
title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm',
|
title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm',
|
||||||
'starting', data['lang']) }}</button>
|
'starting', data['lang']) }}</button>
|
||||||
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
||||||
data['lang']) %}</button>
|
data['lang']) %}</button>
|
||||||
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
{% elif data['importing'] %}
|
{% elif data['importing'] %}
|
||||||
<div id="control_buttons"
|
<div id="control_buttons"
|
||||||
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
style="visibility: visible">
|
style="visibility: visible">
|
||||||
<button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;"
|
<button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;"
|
||||||
class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
|
class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
|
||||||
translate('serverTerm', 'importing',
|
translate('serverTerm', 'importing',
|
||||||
data['lang']) }}</button>
|
data['lang']) }}</button>
|
||||||
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
||||||
data['lang']) %}</button>
|
data['lang']) %}</button>
|
||||||
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div id="control_buttons"
|
<div id="control_buttons"
|
||||||
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
style="visibility: visible">
|
style="visibility: visible">
|
||||||
<button onclick="send_command(serverId, 'start_server');" id="start-btn" style="max-width: 7rem;"
|
<button onclick="send_command(serverId, 'start_server');" id="start-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
|
class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
|
||||||
<button onclick="send_command(serverId, 'restart_server');" id="restart-btn" style="max-width: 7rem;"
|
<button onclick="send_command(serverId, 'restart_server');" id="restart-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang'])
|
class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang'])
|
||||||
%}</button>
|
%}</button>
|
||||||
<button onclick="send_command(serverId, 'stop_server');" id="stop-btn" style="max-width: 7rem;"
|
<button onclick="send_command(serverId, 'stop_server');" id="stop-btn" style="max-width: 7rem;"
|
||||||
class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||||
</div>
|
</div>
|
||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -150,7 +151,7 @@
|
|||||||
/* IE and Edge */
|
/* IE and Edge */
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
/* Firefox */
|
/* Firefox */
|
||||||
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
|
font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -175,7 +176,7 @@
|
|||||||
stopBtn.setAttribute('disabled', 'disabled');
|
stopBtn.setAttribute('disabled', 'disabled');
|
||||||
}
|
}
|
||||||
//<!-- this getCookie function is in base.html-->
|
//<!-- this getCookie function is in base.html-->
|
||||||
var token = getCookie("_xsrf");
|
const token = getCookie("_xsrf");
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -195,12 +196,10 @@
|
|||||||
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data["lang"]) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>';
|
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data["lang"]) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (updateButton.server_id == serverId) {
|
||||||
if (updateButton.server_id == serverId) {
|
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(serverId, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data["lang"]) }}</button><button onclick="send_command(serverId, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>';
|
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(serverId, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data["lang"]) }}</button><button onclick="send_command(serverId, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Convert running to lower case (example: 'True' converts to 'true') and
|
// Convert running to lower case (example: 'True' converts to 'true') and
|
||||||
@ -229,17 +228,31 @@
|
|||||||
}
|
}
|
||||||
//{% end %}
|
//{% end %}
|
||||||
|
|
||||||
function get_server_log() {
|
async function get_server_log() {
|
||||||
$.ajax({
|
const token = getCookie("_xsrf")
|
||||||
type: 'GET',
|
let colors = true;
|
||||||
url: '/ajax/server_log?id=' + serverId,
|
let res = await fetch(`/api/v2/servers/${serverId}/logs?colors=${colors}`, {
|
||||||
dataType: 'text',
|
method: 'GET',
|
||||||
success: function (data) {
|
headers: {
|
||||||
console.log('Got Log From Server')
|
'X-XSRFToken': token
|
||||||
$('#virt_console').html(data);
|
|
||||||
scrollConsole();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
let html = ``
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
for (let value of responseData.data) {
|
||||||
|
html += `<span class='box'>${value}<br /></span>`
|
||||||
|
}
|
||||||
|
console.log('Got Log From Server')
|
||||||
|
$('#virt_console').html(html);
|
||||||
|
scrollConsole();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.status,
|
||||||
|
message: responseData.error
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -258,7 +271,7 @@
|
|||||||
|
|
||||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
let r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||||
return r ? r[1] : undefined;
|
return r ? r[1] : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +306,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function scrollConsole() {
|
function scrollConsole() {
|
||||||
var logview = $('#virt_console');
|
let logview = $('#virt_console');
|
||||||
if (logview.length)
|
if (logview.length)
|
||||||
logview.scrollTop(logview[0].scrollHeight - logview.height());
|
logview.scrollTop(logview[0].scrollHeight - logview.height());
|
||||||
}
|
}
|
||||||
@ -372,7 +385,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
var scrolled = false;
|
|
||||||
$('#virt_console').on('scroll', chkScroll);
|
$('#virt_console').on('scroll', chkScroll);
|
||||||
$('#to-bottom').on('click', scrollToBottom)
|
$('#to-bottom').on('click', scrollToBottom)
|
||||||
});
|
});
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<!-- Desktop View -->
|
||||||
|
<div class="d-none d-sm-block content-wrapper">
|
||||||
<!-- Page Title Header Starts-->
|
<!-- Page Title Header Starts-->
|
||||||
<div class="row page-title-header">
|
<div class="row page-title-header">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@ -16,35 +16,45 @@
|
|||||||
<h4 class="page-title">{{ translate('sidebar', 'documentation', data['lang']) }}</h4>
|
<h4 class="page-title">{{ translate('sidebar', 'documentation', data['lang']) }}</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row iframe-row">
|
||||||
<div class="col-md-12 grid-margin">
|
<div class="col-12 iframe-col">
|
||||||
<iframe src="https://docs.craftycontrol.com/" width=100% height=1100px title="crafty's docs"></iframe>
|
<div class="iframe-wrapper">
|
||||||
|
<iframe title="crafty's docs" src="https://docs.craftycontrol.com/" class="iframe-item"></iframe>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- content-wrapper ends -->
|
</div>
|
||||||
<style>
|
<!-- Mobile View -->
|
||||||
.popover-body {
|
<div class="d-sm-none content-wrapper mobile-content-wrapper">
|
||||||
color: white !important;
|
<iframe title="crafty's docs" src="https://docs.craftycontrol.com/" class="iframe-item"></iframe>
|
||||||
;
|
</div>
|
||||||
}
|
<!-- content-wrapper ends -->
|
||||||
|
<style>
|
||||||
|
.iframe-item {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
#desc_id {
|
.iframe-wrapper {
|
||||||
-ms-overflow-style: none;
|
height: 100%;
|
||||||
/* for Internet Explorer, Edge */
|
}
|
||||||
scrollbar-width: none;
|
|
||||||
/* for Firefox */
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
#desc_id::-webkit-scrollbar {
|
.iframe-col {
|
||||||
display: none;
|
height: 100%;
|
||||||
/* for Chrome, Safari, and Opera */
|
}
|
||||||
}
|
|
||||||
</style>
|
.iframe-row {
|
||||||
|
height: 100%;
|
||||||
|
max-height: calc(100% - 63px);
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-content-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
@ -156,7 +156,7 @@
|
|||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
|
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
|
||||||
.then(function (registration) {
|
.then(function (registration) {
|
||||||
console.error('Service Worker Registered');
|
console.log('Service Worker Registered');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -97,6 +97,7 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
<div class="accordion" id="accordionServers">
|
<div class="accordion" id="accordionServers">
|
||||||
{% for server in data['servers'] %}
|
{% for server in data['servers'] %}
|
||||||
|
{% if server['server_data']['show_status'] %}
|
||||||
<div class="card mb-0">
|
<div class="card mb-0">
|
||||||
<div class="card-header" id="heading-{{server['server_data']['server_id']}}">
|
<div class="card-header" id="heading-{{server['server_data']['server_id']}}">
|
||||||
<h2 class="mb-0 container overflow-hidden">
|
<h2 class="mb-0 container overflow-hidden">
|
||||||
@ -167,6 +168,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% end %}
|
{% end %}
|
||||||
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,136 +1,167 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ data['lang_page'] }}" class="default">
|
<html lang="{{ data['lang_page'] }}" class="default">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||||
|
/>
|
||||||
|
{% block meta %}{% end %}
|
||||||
|
<title>{% block title %}{{ _('Default') }}{% end %}</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="stylesheet"
|
||||||
|
href="/static/assets/vendors/fontawesome6/css/all.css"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/static/assets/crafty.webmanifest" />
|
||||||
|
|
||||||
<head>
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<!-- Required meta tags -->
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta charset="utf-8">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="apple-mobile-web-app-title" content="Crafty" />
|
||||||
{% block meta %}{% end %}
|
<link
|
||||||
<title>{% block title %}{{ _('Default') }}{% end %}</title>
|
rel="apple-touch-icon"
|
||||||
<!-- plugins:css -->
|
href="../static/assets/images/Crafty_4-0.png"
|
||||||
<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">
|
<!-- endinject -->
|
||||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
<!-- Plugin css for this page -->
|
||||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
<!-- End Plugin css for this page -->
|
||||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
<!-- Layout styles -->
|
||||||
<link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css">
|
<link rel="stylesheet" href="/static/assets/css/dark/style.css" />
|
||||||
<link rel="manifest" href="/static/assets/crafty.webmanifest">
|
<!-- 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>
|
||||||
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<body>
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<div class="container-scroller">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||||
<meta name="apple-mobile-web-app-title" content="Crafty">
|
<div
|
||||||
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
|
class="content-wrapper d-flex align-items-sm-center auth auth-bg-1 theme-one"
|
||||||
<!-- endinject -->
|
>
|
||||||
<!-- Plugin css for this page -->
|
<div class="mx-auto">
|
||||||
<!-- End Plugin css for this page -->
|
<div class="auto-form-wrapper">{% block content %} {% end %}</div>
|
||||||
<!-- 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>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container-scroller">
|
|
||||||
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
|
||||||
<div class="content-wrapper d-flex align-items-sm-center auth auth-bg-1 theme-one">
|
|
||||||
<div class="mx-auto">
|
|
||||||
<div class="auto-form-wrapper">
|
|
||||||
{% block content %}
|
|
||||||
{% end %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- content-wrapper ends -->
|
||||||
</div>
|
</div>
|
||||||
<!-- content-wrapper ends -->
|
<!-- page-body-wrapper ends -->
|
||||||
</div>
|
</div>
|
||||||
<!-- page-body-wrapper ends -->
|
<!-- container-scroller -->
|
||||||
</div>
|
<!-- plugins:js -->
|
||||||
<!-- container-scroller -->
|
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||||
<!-- plugins:js -->
|
<!-- endinject -->
|
||||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
<!-- inject:js -->
|
||||||
<!-- endinject -->
|
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||||
<!-- inject:js -->
|
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
<script src="/static/assets/js/shared/misc.js"></script>
|
||||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
<!-- endinject -->
|
||||||
<script src="/static/assets/js/shared/misc.js"></script>
|
<script>
|
||||||
<!-- endinject -->
|
// {% if request.protocol == 'https' %}
|
||||||
<script>
|
let usingWebSockets = true;
|
||||||
|
|
||||||
// {% if request.protocol == 'https' %}
|
let listenEvents = [];
|
||||||
let usingWebSockets = true;
|
|
||||||
|
|
||||||
let listenEvents = [];
|
let pageQueryParams, page;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
|
pageQueryParams =
|
||||||
page = 'page=' + encodeURIComponent(location.pathname)
|
"page_query_params=" + encodeURIComponent(location.search);
|
||||||
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
|
page = "page=" + encodeURIComponent(location.pathname);
|
||||||
wsInternal.onopen = function () {
|
var wsInternal = new WebSocket(
|
||||||
console.log('opened WebSocket connection:', wsInternal)
|
"wss://" + location.host + "/ws?" + page + "&" + pageQueryParams
|
||||||
};
|
);
|
||||||
wsInternal.onmessage = function (rawMessage) {
|
wsInternal.onopen = function () {
|
||||||
var message = JSON.parse(rawMessage.data);
|
console.log("opened WebSocket connection:", wsInternal);
|
||||||
|
};
|
||||||
|
wsInternal.onmessage = function (rawMessage) {
|
||||||
|
var message = JSON.parse(rawMessage.data);
|
||||||
|
|
||||||
console.log('got message: ', message)
|
console.log("got message: ", message);
|
||||||
|
|
||||||
listenEvents
|
listenEvents
|
||||||
.filter(listenedEvent => listenedEvent.event == message.event)
|
.filter((listenedEvent) => listenedEvent.event == message.event)
|
||||||
.forEach(listenedEvent => listenedEvent.callback(message.data))
|
.forEach((listenedEvent) => listenedEvent.callback(message.data));
|
||||||
};
|
};
|
||||||
wsInternal.onerror = function (errorEvent) {
|
wsInternal.onerror = function (errorEvent) {
|
||||||
console.error('WebSocket Error', errorEvent);
|
console.error("WebSocket Error", errorEvent);
|
||||||
};
|
};
|
||||||
wsInternal.onclose = function (closeEvent) {
|
wsInternal.onclose = function (closeEvent) {
|
||||||
console.log('Closed WebSocket', closeEvent);
|
console.log("Closed WebSocket", closeEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
webSocket = {
|
||||||
|
on: function (event, callback) {
|
||||||
|
console.log("registered " + event + " event");
|
||||||
|
listenEvents.push({ event: event, callback: callback });
|
||||||
|
},
|
||||||
|
emit: function (event, data) {
|
||||||
|
var message = {
|
||||||
|
event: event,
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
|
||||||
webSocket = {
|
wsInternal.send(JSON.stringify(message));
|
||||||
on: function (event, callback) {
|
},
|
||||||
console.log('registered ' + event + ' event');
|
};
|
||||||
listenEvents.push({ event: event, callback: callback })
|
} catch (error) {
|
||||||
},
|
console.error("Error while making websocket helpers", error);
|
||||||
emit: function (event, data) {
|
usingWebSockets = false;
|
||||||
var message = {
|
|
||||||
event: event,
|
|
||||||
data: data
|
|
||||||
}
|
|
||||||
|
|
||||||
wsInternal.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
// {% else %}
|
||||||
console.error('Error while making websocket helpers', error);
|
|
||||||
usingWebSockets = false;
|
usingWebSockets = false;
|
||||||
}
|
warn(
|
||||||
// {% else %}
|
"WebSockets are not supported in Crafty if not using the https protocol"
|
||||||
usingWebSockets = false;
|
);
|
||||||
warn('WebSockets are not supported in Crafty if not using the https protocol')
|
var webSocket;
|
||||||
var webSocket;
|
// {% end%}
|
||||||
// {% end%}
|
</script>
|
||||||
|
{% block js %}
|
||||||
</script>
|
<!-- Custom js for this page -->
|
||||||
{% block js %}
|
<script>
|
||||||
<!-- Custom js for this page -->
|
$(document).ready(function () {
|
||||||
<script>
|
let login_opacity_div = document.getElementById("login_opacity");
|
||||||
$(document).ready(function () {
|
let opacity = login_opacity_div.getAttribute("data-value");
|
||||||
let login_opacity_div = document.getElementById('login_opacity');
|
document.getElementById("login-form-background").style.background =
|
||||||
let opacity = login_opacity_div.getAttribute('data-value');
|
"rgb(34, 36, 55, " + opacity / 100 + ")";
|
||||||
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
|
//Register Service worker for mobile app
|
||||||
//Register Service worker for mobile app
|
if ("serviceWorker" in navigator) {
|
||||||
if ('serviceWorker' in navigator) {
|
navigator.serviceWorker
|
||||||
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
|
.register("/static/assets/js/shared/service-worker.js", {
|
||||||
.then(function (registration) {
|
scope: "/",
|
||||||
console.error('Service Worker Registered');
|
})
|
||||||
});
|
.then(function (registration) {
|
||||||
}
|
console.log("Service Worker Registered");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
</script>
|
});
|
||||||
<!-- End custom js for this page -->
|
</script>
|
||||||
{% end %}
|
<!-- End custom js for this page -->
|
||||||
|
{% end %}
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
||||||
</html>
|
|
||||||
|
@ -24,81 +24,89 @@
|
|||||||
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
|
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<form method="post" name="create_server" class="server-wizard" onSubmit="wait_msg()">
|
<form method="post" id="download_exe" name="create_server" class="server-wizard">
|
||||||
{% if data["server_api"] and data["online"] %}
|
{% if data["server_api"] and data["online"] %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{% else %}
|
{% else %}
|
||||||
<fieldset disabled="disabled">
|
<fieldset disabled="disabled">
|
||||||
<style>
|
<style>
|
||||||
.api-alert{
|
.api-alert {
|
||||||
position:absolute;
|
position: absolute;
|
||||||
top:-5px;
|
top: -5px;
|
||||||
left:0;
|
left: 0;
|
||||||
font-size: 50px !important;
|
font-size: 50px !important;
|
||||||
color:#fff;
|
color: #fff;
|
||||||
background:rgb(127, 133, 133);
|
background: rgb(127, 133, 133);
|
||||||
opacity:.4;
|
opacity: .4;
|
||||||
width:100%;
|
width: 100%;
|
||||||
height:100%;
|
height: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
.api-alert p {
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20px;
|
|
||||||
-ms-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% end %}
|
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
|
||||||
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
.api-alert p {
|
||||||
<div id="accordion-1">
|
margin: 0;
|
||||||
<div class="card">
|
position: absolute;
|
||||||
<div class="card-header p-2" id="Role-1">
|
top: 50%;
|
||||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" aria-controls="collapseRole-1">
|
left: 50%;
|
||||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
|
text-align: center;
|
||||||
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
font-size: 20px;
|
||||||
data['lang']) }}</small>
|
-ms-transform: translate(-50%, -50%);
|
||||||
</p>
|
transform: translate(-50%, -50%);
|
||||||
</div>
|
}
|
||||||
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent="">
|
</style>
|
||||||
<div class="card-body scroll">
|
{% end %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{% for r in data['roles'] %}
|
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
<input type="text" class="form-control" id="server_name" name="name"
|
||||||
{{ r['role_name'].capitalize() }}</label></span>
|
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||||
{% end %}
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div id="accordion-1">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header p-2" id="Role-1">
|
||||||
|
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true"
|
||||||
|
aria-controls="collapseRole-1">
|
||||||
|
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
|
||||||
|
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
||||||
|
data['lang']) }}</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent="">
|
||||||
|
<div class="card-body scroll">
|
||||||
|
<div class="form-group">
|
||||||
|
{% for r in data['roles'] %}
|
||||||
|
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
|
||||||
|
type="checkbox">
|
||||||
|
{{ r['role_name'].capitalize() }}</label></span>
|
||||||
|
{% end %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button onclick="eula_confirm()" type="button" class="btn btn-primary mr-2">{{ translate('serverWizard',
|
||||||
|
'buildServer',
|
||||||
|
data['lang']) }}</button>
|
||||||
|
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
|
||||||
|
}}</button>
|
||||||
|
</fieldset>
|
||||||
|
{% if not data["server_api"] and data["online"] %}
|
||||||
|
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
|
||||||
|
<p style="color: white !important;"><i class="fas fa-exclamation-triangle"
|
||||||
|
style="color: red;"></i> {{ translate('error', 'bedrockError', data['lang']) }}<a
|
||||||
|
style="color: red;" ; href="https://status.craftycontrol.com/status/craftycontrol" target="_blank"
|
||||||
|
rel="noopener noreferrer"> {{ translate('error', 'craftyStatus', data['lang']) }}</a>
|
||||||
|
{{ translate('error', 'serverJars2', data['lang']) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% end %}
|
||||||
<button onclick="eula_confirm()" type="button" class="btn btn-primary mr-2">{{ translate('serverWizard',
|
{% if not data["online"] %}
|
||||||
'buildServer',
|
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
|
||||||
data['lang']) }}</button>
|
<p style="color: white !important;"><i class="fas fa-exclamation-triangle"
|
||||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
|
style="color: red;"></i> {{ translate('error', 'noInternet', data['lang']) }}</p>
|
||||||
}}</button>
|
</div>
|
||||||
</fieldset>
|
{% end %}
|
||||||
{% if not data["server_api"] and data["online"] %}
|
|
||||||
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
|
|
||||||
<p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: red;"></i> {{ translate('error', 'bedrockError', data['lang']) }}<a style="color: red;"; href="https://status.craftycontrol.com/status/craftycontrol"
|
|
||||||
target="_blank"> {{ translate('error', 'craftyStatus', data['lang']) }}</a>
|
|
||||||
{{ translate('error', 'serverJars2', data['lang']) }}</p></div>
|
|
||||||
{% end %}
|
|
||||||
{% if not data["online"] %}
|
|
||||||
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
|
|
||||||
<p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: red;"></i> {{ translate('error', 'noInternet', data['lang']) }}</p></div>
|
|
||||||
{% end %}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -110,36 +118,40 @@
|
|||||||
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
|
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<form method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
<form method="post" id="import-jar" class="server-wizard">
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<input type="hidden" value="import_jar" name="create_type">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
<input type="text" class="form-control" id="server_name" name="name" value=""
|
||||||
|
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{
|
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{
|
||||||
translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
|
translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
|
||||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server" required>
|
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
|
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
|
||||||
|
placeholder="bedrock_server" required>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
|
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small
|
||||||
|
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
|
||||||
data['lang']) }}</small></h4>
|
data['lang']) }}</small></h4>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }}
|
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }}
|
||||||
<small></small></label>
|
<small></small></label>
|
||||||
<input type="number" class="form-control" id="port2" name="port" value="19132" step="1" min="1" max="65535" required>
|
<input type="number" class="form-control" id="port2" name="port" value="19132" step="1" min="1"
|
||||||
|
max="65535" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div id="accordion-2">
|
<div id="accordion-2">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header p-2" id="Role-2">
|
<div class="card-header p-2" id="Role-2">
|
||||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
|
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true"
|
||||||
|
aria-controls="collapseRole-2">
|
||||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
|
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
|
||||||
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
||||||
data['lang']) }}</small>
|
data['lang']) }}</small>
|
||||||
@ -172,19 +184,19 @@
|
|||||||
|
|
||||||
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
|
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
|
||||||
<br />
|
<br />
|
||||||
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
<form name="zip" id="import-zip" method="post" class="server-wizard">
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<input type="hidden" value="import_zip" name="create_type">
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
<input type="text" class="form-control" id="server_name" name="name" value=""
|
||||||
|
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{
|
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{
|
||||||
translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
|
translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
|
||||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip" required>
|
<input type="text" class="form-control" id="zip_server_path" name="server_path"
|
||||||
|
placeholder="/var/opt/server.zip" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -196,21 +208,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
|
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
|
||||||
|
placeholder="bedrock_server" required>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small>
|
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small
|
||||||
|
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang'])
|
||||||
|
}}</small>
|
||||||
</h4>
|
</h4>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }}
|
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }}
|
||||||
<small></small></label>
|
<small></small></label>
|
||||||
<input type="number" class="form-control" id="port3" name="port" value="19132" step="1" min="1" max="65535" required>
|
<input type="number" class="form-control" id="port3" name="port" value="19132" step="1" min="1"
|
||||||
|
max="65535" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div id="accordion-3">
|
<div id="accordion-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header p-2" id="Role-3">
|
<div class="card-header p-2" id="Role-3">
|
||||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
|
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true"
|
||||||
|
aria-controls="collapseRole-3">
|
||||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang'])
|
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang'])
|
||||||
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
||||||
data['lang']) }}</small>
|
data['lang']) }}</small>
|
||||||
@ -234,7 +251,8 @@
|
|||||||
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
|
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
|
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
|
||||||
|
aria-hidden="true">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -245,8 +263,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
|
<div class="tree-ctx-item" id="main-tree-div" data-path=""
|
||||||
<input type="radio" id="main-tree-input" name="root_path" value="" checked>
|
style="overflow: scroll; max-height:75%;">
|
||||||
|
<input type="radio" class="root-input" id="main-tree-input" name="root_path" value="" checked>
|
||||||
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
|
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
|
||||||
<i class="far fa-folder"></i>
|
<i class="far fa-folder"></i>
|
||||||
<i class="far fa-folder-open"></i>
|
<i class="far fa-folder-open"></i>
|
||||||
@ -264,7 +283,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
|
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled
|
||||||
|
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
|
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
|
||||||
data['lang'])
|
data['lang'])
|
||||||
@ -281,13 +301,12 @@
|
|||||||
<br />
|
<br />
|
||||||
<p class="card-description">
|
<p class="card-description">
|
||||||
|
|
||||||
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
<form name="zip" id="import-upload" method="post" class="server-wizard">
|
||||||
{% raw xsrf_form_html() %}
|
|
||||||
<input type="hidden" value="import_zip" name="create_type">
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
<input type="text" class="form-control" id="server_name" name="name" value=""
|
||||||
|
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -295,10 +314,12 @@
|
|||||||
<div id="upload_input" class="input-group">
|
<div id="upload_input" class="input-group">
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
<input type="file" multiple="false" class="custom-file-input" id="file" name="file" required>
|
<input type="file" multiple="false" class="custom-file-input" id="file" name="file" required>
|
||||||
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('serverWizard', 'labelZipFile', data['lang']) }}</label>
|
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('serverWizard',
|
||||||
|
'labelZipFile', data['lang']) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()" disabled>{{ translate('serverWizard',
|
<button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()"
|
||||||
|
disabled>{{ translate('serverWizard',
|
||||||
'uploadButton', data['lang']) }}</button>
|
'uploadButton', data['lang']) }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -315,24 +336,28 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
|
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
|
||||||
|
placeholder="paper.jar" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
|
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small
|
||||||
|
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
|
||||||
data['lang']) }}</small></h4>
|
data['lang']) }}</small></h4>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
|
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
|
||||||
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||||
<input type="number" class="form-control" id="port4" name="port" value="19132" step="1" min="1" max="65535" required>
|
<input type="number" class="form-control" id="port4" name="port" value="19132" step="1" min="1"
|
||||||
|
max="65535" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div id="accordion-3">
|
<div id="accordion-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header p-2" id="Role-3">
|
<div class="card-header p-2" id="Role-3">
|
||||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
|
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true"
|
||||||
|
aria-controls="collapseRole-3">
|
||||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole',
|
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole',
|
||||||
data['lang'])
|
data['lang'])
|
||||||
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
|
||||||
@ -343,7 +368,8 @@
|
|||||||
<div class="card-body scroll">
|
<div class="card-body scroll">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{% for r in data['roles'] %}
|
{% for r in data['roles'] %}
|
||||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
|
||||||
|
type="checkbox">
|
||||||
{{ r['role_name'].capitalize() }}</label></span>
|
{{ r['role_name'].capitalize() }}</label></span>
|
||||||
{% end %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
@ -357,7 +383,8 @@
|
|||||||
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
|
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="dir_upload_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
|
<div class="modal fade" id="dir_upload_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
|
||||||
|
aria-hidden="true">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -368,8 +395,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="tree-ctx-item" id="main-tree-div-upload" data-path="" style="overflow: scroll; max-height:75%;">
|
<div class="tree-ctx-item" id="main-tree-div-upload" data-path=""
|
||||||
<input type="radio" id="main-tree-input-upload" name="root_path" value="" checked>
|
style="overflow: scroll; max-height:75%;">
|
||||||
|
<input type="radio" class="root-input" id="main-tree-input-upload" name="root_path" value=""
|
||||||
|
checked>
|
||||||
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
|
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
|
||||||
<i class="far fa-folder"></i>
|
<i class="far fa-folder"></i>
|
||||||
<i class="far fa-folder-open"></i>
|
<i class="far fa-folder-open"></i>
|
||||||
@ -387,7 +416,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="upload_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
|
<button id="upload_submit" type="submit" title="You must select server root dir first" disabled
|
||||||
|
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
|
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
|
||||||
data['lang'])
|
data['lang'])
|
||||||
@ -526,7 +556,7 @@
|
|||||||
xmlHttpRequest.addEventListener('load', (event) => {
|
xmlHttpRequest.addEventListener('load', (event) => {
|
||||||
if (event.target.responseText == 'success') {
|
if (event.target.responseText == 'success') {
|
||||||
console.log('Upload for file', file.name, 'was successful!')
|
console.log('Upload for file', file.name, 'was successful!')
|
||||||
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
|
document.getElementById("upload_input").innerHTML = `<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value=${fileName} type="text" id="file-uploaded" disabled></input> 🔒</div>`;
|
||||||
document.getElementById("lower_half").style.visibility = "visible";
|
document.getElementById("lower_half").style.visibility = "visible";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -548,24 +578,20 @@
|
|||||||
xmlHttpRequest.send(file);
|
xmlHttpRequest.send(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("root_upload_button").addEventListener("click", function () {
|
document.getElementById("root_upload_button").addEventListener("click", function (event) {
|
||||||
if (file) {
|
if (file) {
|
||||||
upload = true;
|
upload = true;
|
||||||
if (document.getElementById('root_upload_button').classList.contains('clicked')) {
|
if (document.getElementById('root_upload_button').classList.contains('clicked')) {
|
||||||
document.getElementById('main-tree-div-upload').innerHTML = '<input type="radio" id="main-tree-input-upload" name="root_path" value="" checked><span id="main-tree-upload" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate("serverFiles", "files", data["lang"]) }}</span></input>'
|
document.getElementById('main-tree-div-upload').innerHTML = '<input type="radio" class="root-input" id="main-tree-input-upload" name="root_path" value="" checked><span id="main-tree-upload" class="files-tree-title tree-caret-down root-dir"><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate("serverFiles", "files", data["lang"]) }}</span></input>'
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('root_upload_button').classList.add('clicked')
|
document.getElementById('root_upload_button').classList.add('clicked')
|
||||||
}
|
}
|
||||||
var token = getCookie("_xsrf");
|
const token = getCookie("_xsrf");
|
||||||
var dialog = bootbox.dialog({
|
var dialog = bootbox.dialog({
|
||||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||||
closeButton: false
|
closeButton: false
|
||||||
});
|
});
|
||||||
$.ajax({
|
getDirView();
|
||||||
type: "POST",
|
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: '/ajax/unzip_server?id=-1&file=' + encodeURIComponent(file.name),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
bootbox.alert("You must input a path before selecting this button");
|
bootbox.alert("You must input a path before selecting this button");
|
||||||
}
|
}
|
||||||
@ -587,7 +613,7 @@
|
|||||||
},
|
},
|
||||||
callback: function (result) {
|
callback: function (result) {
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
document.create_server.submit();
|
$("#download_exe").submit();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
@ -600,25 +626,19 @@
|
|||||||
$(".tree-reset").on("click", function () {
|
$(".tree-reset").on("click", function () {
|
||||||
location.href = "/server/bedrock_step1";
|
location.href = "/server/bedrock_step1";
|
||||||
});
|
});
|
||||||
document.getElementById("root_files_button").addEventListener("click", function () {
|
document.getElementById("root_files_button").addEventListener("click", function (event) {
|
||||||
if (document.forms["zip"]["server_path"].value != "") {
|
if (document.forms["zip"]["server_path"].value != "") {
|
||||||
if (document.getElementById('root_files_button').classList.contains('clicked')) {
|
if (document.getElementById('root_files_button').classList.contains('clicked')) {
|
||||||
document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input" name="root_path" value="" checked><span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate('serverFiles', 'files', data['lang']) }}</span></input>'
|
show_file_tree();
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('root_files_button').classList.add('clicked')
|
document.getElementById('root_files_button').classList.add('clicked')
|
||||||
}
|
}
|
||||||
path = document.forms["zip"]["server_path"].value;
|
|
||||||
console.log(document.forms["zip"]["server_path"].value)
|
|
||||||
var token = getCookie("_xsrf");
|
|
||||||
var dialog = bootbox.dialog({
|
var dialog = bootbox.dialog({
|
||||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||||
closeButton: false
|
closeButton: false
|
||||||
});
|
});
|
||||||
$.ajax({
|
getDirView();
|
||||||
type: "POST",
|
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
url: '/ajax/unzip_server?id=-1&path=' + encodeURIComponent(path),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
bootbox.alert("You must input a path before selecting this button");
|
bootbox.alert("You must input a path before selecting this button");
|
||||||
}
|
}
|
||||||
@ -645,134 +665,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_file_tree() {
|
|
||||||
if (upload) {
|
|
||||||
$("#dir_upload_select").modal();
|
|
||||||
} else {
|
|
||||||
$("#dir_select").modal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTreeView(path) {
|
|
||||||
const styles = window.getComputedStyle(document.getElementById("lower_half"));
|
|
||||||
//If this value is still hidden we know the user is executing a zip import and not an upload
|
|
||||||
if (styles.visibility === "hidden") {
|
|
||||||
document.getElementById('zip_submit').disabled = false;
|
|
||||||
} else {
|
|
||||||
document.getElementById('upload_submit').disabled = false;
|
|
||||||
}
|
|
||||||
path = path
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: '/ajax/get_zip_tree?id=-1&path=' + path,
|
|
||||||
dataType: 'text',
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
dataArr = data.split('\n');
|
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
||||||
text = dataArr.join('\n');
|
|
||||||
if (styles.visibility === "hidden") {
|
|
||||||
try {
|
|
||||||
document.getElementById('main-tree-div').innerHTML += text;
|
|
||||||
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
|
||||||
} catch {
|
|
||||||
document.getElementById('files-tree').innerHTML = text;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
document.getElementById('main-tree-div-upload').innerHTML += text;
|
|
||||||
document.getElementById('main-tree-upload').parentElement.classList.add("clicked");
|
|
||||||
} catch {
|
|
||||||
document.getElementById('files-tree').innerHTML = text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getToggleMain(event) {
|
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
document.getElementById("files-tree").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getDirView(event) {
|
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
|
|
||||||
if (document.getElementById(path).classList.contains('clicked')) {
|
|
||||||
|
|
||||||
var toggler = document.getElementById(path + "span");
|
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
|
||||||
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: '/ajax/get_zip_dir?id=-1&path=' + path,
|
|
||||||
dataType: 'text',
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
|
|
||||||
dataArr = data.split('\n');
|
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
||||||
text = dataArr.join('\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
document.getElementById(path + "span").classList.add('tree-caret-down');
|
|
||||||
document.getElementById(path).innerHTML += text;
|
|
||||||
document.getElementById(path).classList.add("clicked");
|
|
||||||
} catch {
|
|
||||||
console.log("Bad")
|
|
||||||
}
|
|
||||||
|
|
||||||
var toggler = document.getElementById(path);
|
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
|
||||||
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
|
||||||
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (webSocket) {
|
|
||||||
webSocket.on('send_temp_path', function (data) {
|
|
||||||
setTimeout(function () {
|
|
||||||
var x = document.querySelector('.bootbox');
|
|
||||||
if (x) {
|
|
||||||
x.remove()
|
|
||||||
}
|
|
||||||
var x = document.querySelector('.modal-backdrop');
|
|
||||||
if (x) {
|
|
||||||
x.remove()
|
|
||||||
}
|
|
||||||
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
|
||||||
document.getElementById('main-tree-input-upload').setAttribute('value', data.path)
|
|
||||||
getTreeView(data.path);
|
|
||||||
show_file_tree();
|
|
||||||
$("#root_files_button").attr("disabled", "disabled");
|
|
||||||
$("#root_upload_button").attr("disabled", "disabled");
|
|
||||||
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$('#file').change(function () {
|
$('#file').change(function () {
|
||||||
console.log("File changed");
|
console.log("File changed");
|
||||||
if ($('#file').val()) {
|
if ($('#file').val()) {
|
||||||
@ -781,6 +673,187 @@
|
|||||||
console.log("File changed good");
|
console.log("File changed good");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
function replacer(key, value) {
|
||||||
|
if (key === "roles") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (key != "ignored_exits") {
|
||||||
|
if (typeof value == "boolean" || key === "host" || key === "version") {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return (isNaN(value) ? value : +value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function calcRoles() {
|
||||||
|
let role_ids = $('.roles').map(function () {
|
||||||
|
if ($(this).is(':checked')) {
|
||||||
|
return $(this).val();
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
console.log(role_ids)
|
||||||
|
return role_ids
|
||||||
|
}
|
||||||
|
async function send_server(data) {
|
||||||
|
let token = getCookie("_xsrf")
|
||||||
|
console.log(token)
|
||||||
|
let res = await fetch(`/api/v2/servers/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
window.location.href = '/panel/dashboard';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
$("#download_exe").on("submit", async function (e) {
|
||||||
|
wait_msg();
|
||||||
|
e.preventDefault();
|
||||||
|
let jarForm = document.getElementById("download_exe");
|
||||||
|
|
||||||
|
let formData = new FormData(jarForm);
|
||||||
|
//Create an object from the form data entries
|
||||||
|
let formDataObject = Object.fromEntries(formData.entries());
|
||||||
|
console.log(formDataObject);
|
||||||
|
let send_data = {
|
||||||
|
"name": formDataObject.name,
|
||||||
|
"roles": calcRoles(),
|
||||||
|
"monitoring_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_monitoring_data": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 19132
|
||||||
|
},
|
||||||
|
"create_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_create_data": {
|
||||||
|
"create_type": "download_exe",
|
||||||
|
"download_exe_create_data": {
|
||||||
|
//agree to eula since we confirmed before calling this function
|
||||||
|
"agree_to_eula": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(send_data);
|
||||||
|
// Format the plain form data as JSON
|
||||||
|
let formDataJsonString = JSON.stringify(send_data, replacer);
|
||||||
|
|
||||||
|
|
||||||
|
console.log(formDataJsonString);
|
||||||
|
|
||||||
|
send_server(formDataJsonString);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#import-jar").on("submit", async function (e) {
|
||||||
|
wait_msg(true);
|
||||||
|
e.preventDefault();
|
||||||
|
let jarForm = document.getElementById("import-jar");
|
||||||
|
|
||||||
|
let formData = new FormData(jarForm);
|
||||||
|
//Create an object from the form data entries
|
||||||
|
let formDataObject = Object.fromEntries(formData.entries());
|
||||||
|
console.log(formDataObject);
|
||||||
|
let send_data = {
|
||||||
|
"name": formDataObject.name,
|
||||||
|
"roles": calcRoles(),
|
||||||
|
"monitoring_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_monitoring_data": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": formDataObject.port
|
||||||
|
},
|
||||||
|
"create_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_create_data": {
|
||||||
|
"create_type": "import_server",
|
||||||
|
"import_server_create_data": {
|
||||||
|
"existing_server_path": formDataObject.server_path,
|
||||||
|
"executable": formDataObject.server_jar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(send_data);
|
||||||
|
// Format the plain form data as JSON
|
||||||
|
let formDataJsonString = JSON.stringify(send_data, replacer);
|
||||||
|
|
||||||
|
send_server(formDataJsonString);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#import-zip").on("submit", async function (e) {
|
||||||
|
wait_msg(true);
|
||||||
|
e.preventDefault();
|
||||||
|
let jarForm = document.getElementById("import-zip");
|
||||||
|
|
||||||
|
let formData = new FormData(jarForm);
|
||||||
|
//Create an object from the form data entries
|
||||||
|
let formDataObject = Object.fromEntries(formData.entries());
|
||||||
|
console.log(formDataObject);
|
||||||
|
let send_data = {
|
||||||
|
"name": formDataObject.name,
|
||||||
|
"roles": calcRoles(),
|
||||||
|
"monitoring_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_monitoring_data": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": formDataObject.port
|
||||||
|
},
|
||||||
|
"create_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_create_data": {
|
||||||
|
"create_type": "import_server",
|
||||||
|
"import_server_create_data": {
|
||||||
|
"existing_server_path": formDataObject.root_path,
|
||||||
|
"executable": formDataObject.server_jar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(send_data);
|
||||||
|
// Format the plain form data as JSON
|
||||||
|
let formDataJsonString = JSON.stringify(send_data, replacer);
|
||||||
|
|
||||||
|
send_server(formDataJsonString);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#import-upload").on("submit", async function (e) {
|
||||||
|
wait_msg(true);
|
||||||
|
e.preventDefault();
|
||||||
|
let jarForm = document.getElementById("import-upload");
|
||||||
|
|
||||||
|
let formData = new FormData(jarForm);
|
||||||
|
//Create an object from the form data entries
|
||||||
|
let formDataObject = Object.fromEntries(formData.entries());
|
||||||
|
console.log(formDataObject);
|
||||||
|
let send_data = {
|
||||||
|
"name": formDataObject.name,
|
||||||
|
"roles": calcRoles(),
|
||||||
|
"monitoring_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_monitoring_data": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": formDataObject.port
|
||||||
|
},
|
||||||
|
"create_type": "minecraft_bedrock",
|
||||||
|
"minecraft_bedrock_create_data": {
|
||||||
|
"create_type": "import_server",
|
||||||
|
"import_server_create_data": {
|
||||||
|
"existing_server_path": formDataObject.root_path,
|
||||||
|
"executable": formDataObject.server_jar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(send_data);
|
||||||
|
// Format the plain form data as JSON
|
||||||
|
let formDataJsonString = JSON.stringify(send_data, replacer);
|
||||||
|
send_server(formDataJsonString);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script type="text/javascript" src="../../static/assets/js/shared/root-dir.js"></script>
|
||||||
{% end %}
|
{% end %}
|
File diff suppressed because it is too large
Load Diff
16
app/migrations/20230901_user_notif.py
Normal file
16
app/migrations/20230901_user_notif.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Generated by database migrator
|
||||||
|
import peewee
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(migrator, database, **kwargs):
|
||||||
|
migrator.add_columns("users", cleared_notifs=peewee.CharField(default=""))
|
||||||
|
"""
|
||||||
|
Write your migrations here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def rollback(migrator, database, **kwargs):
|
||||||
|
migrator.drop_columns("users", ["cleared_notifs"])
|
||||||
|
"""
|
||||||
|
Write your rollback migrations here.
|
||||||
|
"""
|
2
main.py
2
main.py
@ -171,7 +171,7 @@ if __name__ == "__main__":
|
|||||||
Console.info("Remote change complete.")
|
Console.info("Remote change complete.")
|
||||||
|
|
||||||
import3 = Import3(helper, controller)
|
import3 = Import3(helper, controller)
|
||||||
tasks_manager = TasksManager(helper, controller)
|
tasks_manager = TasksManager(helper, controller, file_helper)
|
||||||
tasks_manager.start_webserver()
|
tasks_manager.start_webserver()
|
||||||
|
|
||||||
def signal_handler(signum, _frame):
|
def signal_handler(signum, _frame):
|
||||||
|
@ -5,7 +5,7 @@ bleach==4.1
|
|||||||
cached_property==1.5.2
|
cached_property==1.5.2
|
||||||
colorama==0.4
|
colorama==0.4
|
||||||
croniter==1.3.5
|
croniter==1.3.5
|
||||||
cryptography==41.0.1
|
cryptography==41.0.3
|
||||||
libgravatar==1.0.0
|
libgravatar==1.0.0
|
||||||
peewee==3.13
|
peewee==3.13
|
||||||
pexpect==4.8
|
pexpect==4.8
|
||||||
@ -15,7 +15,7 @@ pyjwt==2.4.0
|
|||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
requests==2.31
|
requests==2.31
|
||||||
termcolor==1.1
|
termcolor==1.1
|
||||||
tornado==6.3.2
|
tornado==6.3.3
|
||||||
tzlocal==4.0
|
tzlocal==4.0
|
||||||
jsonschema==4.5.1
|
jsonschema==4.5.1
|
||||||
orjson==3.8.12
|
orjson==3.8.12
|
||||||
|
@ -3,7 +3,7 @@ sonar.organization=crafty-controller
|
|||||||
|
|
||||||
# This is the name and version displayed in the SonarCloud UI.
|
# This is the name and version displayed in the SonarCloud UI.
|
||||||
sonar.projectName=Crafty 4
|
sonar.projectName=Crafty 4
|
||||||
sonar.projectVersion=4.1.4
|
sonar.projectVersion=4.2.0
|
||||||
sonar.python.version=3.9, 3.10, 3.11
|
sonar.python.version=3.9, 3.10, 3.11
|
||||||
sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**
|
sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user