mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Backup/Config.json rework for API key hardening
See merge request crafty-controller/crafty-4!369
This commit is contained in:
parent
fd162a0d24
commit
7d286e60e0
@ -5,7 +5,7 @@
|
|||||||
### New features
|
### New features
|
||||||
None
|
None
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
None
|
Backup/Config.json rework for API key hardening ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/369))
|
||||||
### Tweaks
|
### Tweaks
|
||||||
Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/370))
|
Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/370))
|
||||||
<br><br>
|
<br><br>
|
||||||
@ -31,10 +31,6 @@ Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/c
|
|||||||
### Bug fixes
|
### Bug fixes
|
||||||
- Fix winreg import pass on non-NT systems ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/344))
|
- Fix winreg import pass on non-NT systems ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/344))
|
||||||
- Make the WebSocket automatically reconnect. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/345))
|
- Make the WebSocket automatically reconnect. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/345))
|
||||||
- Fix an error when there are no servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/346))
|
|
||||||
- Use relative paths for the jarfile and logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/347))
|
|
||||||
- Flatten all instances of username creation or editing, usernames should be lower case.
|
|
||||||
- - ([Merge Request 1](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342))
|
|
||||||
- - ([Merge Request 2](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/351))
|
- - ([Merge Request 2](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/351))
|
||||||
- Add version inheretence & config check ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/353))
|
- Add version inheretence & config check ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/353))
|
||||||
- Fix support log temp file deletion issue/hang ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/354))
|
- Fix support log temp file deletion issue/hang ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/354))
|
||||||
@ -47,6 +43,3 @@ Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/c
|
|||||||
- Remove session.lock warning ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/338))
|
- Remove session.lock warning ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/338))
|
||||||
- Correct Dutch Spacing Issue ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/340))
|
- Correct Dutch Spacing Issue ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/340))
|
||||||
- Remove no-else-* pylint exemptions and tidy code. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342))
|
- Remove no-else-* pylint exemptions and tidy code. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342))
|
||||||
- Make unRAID more readable, and flatten path to lower, to fit standard practice. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/337))
|
|
||||||
- Fix Java Pathing issues on windows ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/343/diffs?commit_id=cda2120579083d447db5dbeb5489822880f4cae7))
|
|
||||||
|
|
||||||
|
@ -17,6 +17,14 @@ class ManagementController:
|
|||||||
def get_latest_hosts_stats():
|
def get_latest_hosts_stats():
|
||||||
return HelpersManagement.get_latest_hosts_stats()
|
return HelpersManagement.get_latest_hosts_stats()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_crafty_api_key(key):
|
||||||
|
HelpersManagement.set_secret_api_key(key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_crafty_api_key():
|
||||||
|
return HelpersManagement.get_secret_api_key()
|
||||||
|
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
# Commands Methods
|
# Commands Methods
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
|
@ -5,6 +5,7 @@ import json
|
|||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from app.classes.controllers.roles_controller import RolesController
|
from app.classes.controllers.roles_controller import RolesController
|
||||||
|
from app.classes.shared.file_helpers import FileHelpers
|
||||||
|
|
||||||
from app.classes.shared.singleton import Singleton
|
from app.classes.shared.singleton import Singleton
|
||||||
from app.classes.shared.server import ServerInstance
|
from app.classes.shared.server import ServerInstance
|
||||||
@ -28,8 +29,9 @@ logger = logging.getLogger(__name__)
|
|||||||
class ServersController(metaclass=Singleton):
|
class ServersController(metaclass=Singleton):
|
||||||
servers_list: ServerInstance
|
servers_list: ServerInstance
|
||||||
|
|
||||||
def __init__(self, helper, servers_helper, management_helper):
|
def __init__(self, helper, servers_helper, management_helper, file_helper):
|
||||||
self.helper: Helpers = helper
|
self.helper: Helpers = helper
|
||||||
|
self.file_helper: FileHelpers = file_helper
|
||||||
self.servers_helper: HelperServers = servers_helper
|
self.servers_helper: HelperServers = servers_helper
|
||||||
self.management_helper = management_helper
|
self.management_helper = management_helper
|
||||||
self.servers_list = []
|
self.servers_list = []
|
||||||
@ -189,6 +191,7 @@ class ServersController(metaclass=Singleton):
|
|||||||
self.helper,
|
self.helper,
|
||||||
self.management_helper,
|
self.management_helper,
|
||||||
self.stats,
|
self.stats,
|
||||||
|
self.file_helper,
|
||||||
),
|
),
|
||||||
"server_settings": settings.props,
|
"server_settings": settings.props,
|
||||||
}
|
}
|
||||||
|
@ -218,7 +218,7 @@ class Stats:
|
|||||||
|
|
||||||
return level_total_size
|
return level_total_size
|
||||||
|
|
||||||
def get_server_players(self, server_id): # pylint: disable=no-self-use
|
def get_server_players(self, server_id):
|
||||||
|
|
||||||
server = HelperServers.get_server_data_by_id(server_id)
|
server = HelperServers.get_server_data_by_id(server_id)
|
||||||
|
|
||||||
|
@ -38,6 +38,16 @@ class AuditLog(BaseModel):
|
|||||||
table_name = "audit_log"
|
table_name = "audit_log"
|
||||||
|
|
||||||
|
|
||||||
|
# **********************************************************************************
|
||||||
|
# Crafty Settings Class
|
||||||
|
# **********************************************************************************
|
||||||
|
class CraftySettings(BaseModel):
|
||||||
|
secret_api_key = CharField(default="")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
table_name = "crafty_settings"
|
||||||
|
|
||||||
|
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
# Host_Stats Class
|
# Host_Stats Class
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
@ -231,6 +241,17 @@ class HelpersManagement:
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_secret_api_key(key):
|
||||||
|
CraftySettings.insert(secret_api_key=key).execute()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_secret_api_key():
|
||||||
|
settings = CraftySettings.select(CraftySettings.secret_api_key).where(
|
||||||
|
CraftySettings.id == 1
|
||||||
|
)
|
||||||
|
return settings[0].secret_api_key
|
||||||
|
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
# Schedules Methods
|
# Schedules Methods
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
|
@ -5,6 +5,7 @@ import jwt
|
|||||||
from jwt import PyJWTError
|
from jwt import PyJWTError
|
||||||
|
|
||||||
from app.classes.models.users import HelperUsers, ApiKeys
|
from app.classes.models.users import HelperUsers, ApiKeys
|
||||||
|
from app.classes.controllers.management_controller import ManagementController
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -13,11 +14,14 @@ class Authentication:
|
|||||||
def __init__(self, helper):
|
def __init__(self, helper):
|
||||||
self.helper = helper
|
self.helper = helper
|
||||||
self.secret = "my secret"
|
self.secret = "my secret"
|
||||||
self.secret = self.helper.get_setting("apikey_secret", None)
|
try:
|
||||||
|
self.secret = ManagementController.get_crafty_api_key()
|
||||||
if self.secret is None or self.secret == "random":
|
if self.secret == "":
|
||||||
|
self.secret = self.helper.random_string_generator(64)
|
||||||
|
ManagementController.set_crafty_api_key(str(self.secret))
|
||||||
|
except:
|
||||||
self.secret = self.helper.random_string_generator(64)
|
self.secret = self.helper.random_string_generator(64)
|
||||||
self.helper.set_setting("apikey_secret", self.secret)
|
ManagementController.set_crafty_api_key(str(self.secret))
|
||||||
|
|
||||||
def generate(self, user_id, extra=None):
|
def generate(self, user_id, extra=None):
|
||||||
if extra is None:
|
if extra is None:
|
||||||
|
@ -2,14 +2,22 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
from zipfile import ZipFile, ZIP_DEFLATED
|
from zipfile import ZipFile, ZIP_DEFLATED
|
||||||
|
|
||||||
|
from app.classes.shared.helpers import Helpers
|
||||||
|
from app.classes.shared.console import Console
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FileHelpers:
|
class FileHelpers:
|
||||||
allowed_quotes = ['"', "'", "`"]
|
allowed_quotes = ['"', "'", "`"]
|
||||||
|
|
||||||
|
def __init__(self, helper):
|
||||||
|
self.helper: Helpers = helper
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def del_dirs(path):
|
def del_dirs(path):
|
||||||
path = pathlib.Path(path)
|
path = pathlib.Path(path)
|
||||||
@ -82,7 +90,6 @@ class FileHelpers:
|
|||||||
f"Error backing up: {os.path.join(root, file)}!"
|
f"Error backing up: {os.path.join(root, file)}!"
|
||||||
f" - Error was: {e}"
|
f" - Error was: {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -113,3 +120,173 @@ class FileHelpers:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def make_compressed_backup(
|
||||||
|
self, path_to_destination, path_to_zip, excluded_dirs, server_id
|
||||||
|
):
|
||||||
|
# create a ZipFile object
|
||||||
|
path_to_destination += ".zip"
|
||||||
|
ex_replace = [p.replace("\\", "/") for p in excluded_dirs]
|
||||||
|
total_bytes = 0
|
||||||
|
dir_bytes = Helpers.get_dir_size(path_to_zip)
|
||||||
|
results = {
|
||||||
|
"percent": 0,
|
||||||
|
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||||
|
}
|
||||||
|
self.helper.websocket_helper.broadcast_page_params(
|
||||||
|
"/panel/server_detail",
|
||||||
|
{"id": str(server_id)},
|
||||||
|
"backup_status",
|
||||||
|
results,
|
||||||
|
)
|
||||||
|
with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file:
|
||||||
|
for root, dirs, files in os.walk(path_to_zip, topdown=True):
|
||||||
|
for l_dir in dirs:
|
||||||
|
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
|
||||||
|
dirs.remove(l_dir)
|
||||||
|
ziproot = path_to_zip
|
||||||
|
for file in files:
|
||||||
|
if (
|
||||||
|
str(os.path.join(root, file)).replace("\\", "/")
|
||||||
|
not in ex_replace
|
||||||
|
and file != "crafty.sqlite"
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
logger.info(f"backing up: {os.path.join(root, file)}")
|
||||||
|
if os.name == "nt":
|
||||||
|
zip_file.write(
|
||||||
|
os.path.join(root, file),
|
||||||
|
os.path.join(root.replace(ziproot, ""), file),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
zip_file.write(
|
||||||
|
os.path.join(root, file),
|
||||||
|
os.path.join(root.replace(ziproot, "/"), file),
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Error backing up: {os.path.join(root, file)}!"
|
||||||
|
f" - Error was: {e}"
|
||||||
|
)
|
||||||
|
total_bytes += os.path.getsize(os.path.join(root, file))
|
||||||
|
percent = round((total_bytes / dir_bytes) * 100, 2)
|
||||||
|
results = {
|
||||||
|
"percent": percent,
|
||||||
|
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||||
|
}
|
||||||
|
self.helper.websocket_helper.broadcast_page_params(
|
||||||
|
"/panel/server_detail",
|
||||||
|
{"id": str(server_id)},
|
||||||
|
"backup_status",
|
||||||
|
results,
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def make_backup(self, path_to_destination, path_to_zip, excluded_dirs, server_id):
|
||||||
|
# create a ZipFile object
|
||||||
|
path_to_destination += ".zip"
|
||||||
|
ex_replace = [p.replace("\\", "/") for p in excluded_dirs]
|
||||||
|
total_bytes = 0
|
||||||
|
dir_bytes = Helpers.get_dir_size(path_to_zip)
|
||||||
|
results = {
|
||||||
|
"percent": 0,
|
||||||
|
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||||
|
}
|
||||||
|
self.helper.websocket_helper.broadcast_page_params(
|
||||||
|
"/panel/server_detail",
|
||||||
|
{"id": str(server_id)},
|
||||||
|
"backup_status",
|
||||||
|
results,
|
||||||
|
)
|
||||||
|
with ZipFile(path_to_destination, "w") as zip_file:
|
||||||
|
for root, dirs, files in os.walk(path_to_zip, topdown=True):
|
||||||
|
for l_dir in dirs:
|
||||||
|
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
|
||||||
|
dirs.remove(l_dir)
|
||||||
|
ziproot = path_to_zip
|
||||||
|
for file in files:
|
||||||
|
if (
|
||||||
|
str(os.path.join(root, file)).replace("\\", "/")
|
||||||
|
not in ex_replace
|
||||||
|
and file != "crafty.sqlite"
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
logger.info(f"backing up: {os.path.join(root, file)}")
|
||||||
|
if os.name == "nt":
|
||||||
|
zip_file.write(
|
||||||
|
os.path.join(root, file),
|
||||||
|
os.path.join(root.replace(ziproot, ""), file),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
zip_file.write(
|
||||||
|
os.path.join(root, file),
|
||||||
|
os.path.join(root.replace(ziproot, "/"), file),
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Error backing up: {os.path.join(root, file)}!"
|
||||||
|
f" - Error was: {e}"
|
||||||
|
)
|
||||||
|
total_bytes += os.path.getsize(os.path.join(root, file))
|
||||||
|
percent = round((total_bytes / dir_bytes) * 100, 2)
|
||||||
|
results = {
|
||||||
|
"percent": percent,
|
||||||
|
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||||
|
}
|
||||||
|
self.helper.websocket_helper.broadcast_page_params(
|
||||||
|
"/panel/server_detail",
|
||||||
|
{"id": str(server_id)},
|
||||||
|
"backup_status",
|
||||||
|
results,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unzip_file(zip_path):
|
||||||
|
new_dir_list = zip_path.split("/")
|
||||||
|
new_dir = ""
|
||||||
|
for i in range(len(new_dir_list) - 1):
|
||||||
|
if i == 0:
|
||||||
|
new_dir += new_dir_list[i]
|
||||||
|
else:
|
||||||
|
new_dir += "/" + new_dir_list[i]
|
||||||
|
|
||||||
|
if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path):
|
||||||
|
Helpers.ensure_dir_exists(new_dir)
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(temp_dir)
|
||||||
|
for i in enumerate(zip_ref.filelist):
|
||||||
|
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
|
||||||
|
i
|
||||||
|
].filename.endswith("/"):
|
||||||
|
break
|
||||||
|
|
||||||
|
full_root_path = temp_dir
|
||||||
|
|
||||||
|
for item in os.listdir(full_root_path):
|
||||||
|
if os.path.isdir(os.path.join(full_root_path, item)):
|
||||||
|
try:
|
||||||
|
FileHelpers.move_dir(
|
||||||
|
os.path.join(full_root_path, item),
|
||||||
|
os.path.join(new_dir, item),
|
||||||
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
FileHelpers.move_file(
|
||||||
|
os.path.join(full_root_path, item),
|
||||||
|
os.path.join(new_dir, item),
|
||||||
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
||||||
|
except Exception as ex:
|
||||||
|
Console.error(ex)
|
||||||
|
else:
|
||||||
|
return "false"
|
||||||
|
return
|
||||||
|
@ -22,7 +22,6 @@ from contextlib import redirect_stderr, suppress
|
|||||||
from app.classes.shared.null_writer import NullWriter
|
from app.classes.shared.null_writer import NullWriter
|
||||||
from app.classes.shared.console import Console
|
from app.classes.shared.console import Console
|
||||||
from app.classes.shared.installer import installer
|
from app.classes.shared.installer import installer
|
||||||
from app.classes.shared.file_helpers import FileHelpers
|
|
||||||
from app.classes.shared.translation import Translation
|
from app.classes.shared.translation import Translation
|
||||||
from app.classes.web.websocket_helper import WebSocketHelper
|
from app.classes.web.websocket_helper import WebSocketHelper
|
||||||
|
|
||||||
@ -441,53 +440,6 @@ class Helpers:
|
|||||||
return ctypes.windll.shell32.IsUserAnAdmin() == 1
|
return ctypes.windll.shell32.IsUserAnAdmin() == 1
|
||||||
return os.geteuid() == 0
|
return os.geteuid() == 0
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unzip_file(zip_path):
|
|
||||||
new_dir_list = zip_path.split("/")
|
|
||||||
new_dir = ""
|
|
||||||
for i in range(len(new_dir_list) - 1):
|
|
||||||
if i == 0:
|
|
||||||
new_dir += new_dir_list[i]
|
|
||||||
else:
|
|
||||||
new_dir += "/" + new_dir_list[i]
|
|
||||||
|
|
||||||
if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path):
|
|
||||||
Helpers.ensure_dir_exists(new_dir)
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(temp_dir)
|
|
||||||
for i in enumerate(zip_ref.filelist):
|
|
||||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
|
|
||||||
i
|
|
||||||
].filename.endswith("/"):
|
|
||||||
break
|
|
||||||
|
|
||||||
full_root_path = temp_dir
|
|
||||||
|
|
||||||
for item in os.listdir(full_root_path):
|
|
||||||
if os.path.isdir(os.path.join(full_root_path, item)):
|
|
||||||
try:
|
|
||||||
FileHelpers.move_dir(
|
|
||||||
os.path.join(full_root_path, item),
|
|
||||||
os.path.join(new_dir, item),
|
|
||||||
)
|
|
||||||
except Exception as ex:
|
|
||||||
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
FileHelpers.move_file(
|
|
||||||
os.path.join(full_root_path, item),
|
|
||||||
os.path.join(new_dir, item),
|
|
||||||
)
|
|
||||||
except Exception as ex:
|
|
||||||
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
|
|
||||||
except Exception as ex:
|
|
||||||
Console.error(ex)
|
|
||||||
else:
|
|
||||||
return "false"
|
|
||||||
return
|
|
||||||
|
|
||||||
def ensure_logging_setup(self):
|
def ensure_logging_setup(self):
|
||||||
log_file = os.path.join(os.path.curdir, "logs", "commander.log")
|
log_file = os.path.join(os.path.curdir, "logs", "commander.log")
|
||||||
session_log_file = os.path.join(os.path.curdir, "logs", "session.log")
|
session_log_file = os.path.join(os.path.curdir, "logs", "session.log")
|
||||||
@ -832,7 +784,7 @@ class Helpers:
|
|||||||
for item in file_list:
|
for item in file_list:
|
||||||
if os.path.isdir(os.path.join(folder, item)):
|
if os.path.isdir(os.path.join(folder, item)):
|
||||||
dir_list.append(item)
|
dir_list.append(item)
|
||||||
else:
|
elif str(item) != "crafty.sqlite":
|
||||||
unsorted_files.append(item)
|
unsorted_files.append(item)
|
||||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||||
unsorted_files, key=str.casefold
|
unsorted_files, key=str.casefold
|
||||||
@ -863,13 +815,14 @@ class Helpers:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_dir(folder, output=""):
|
def generate_dir(folder, output=""):
|
||||||
|
|
||||||
dir_list = []
|
dir_list = []
|
||||||
unsorted_files = []
|
unsorted_files = []
|
||||||
file_list = os.listdir(folder)
|
file_list = os.listdir(folder)
|
||||||
for item in file_list:
|
for item in file_list:
|
||||||
if os.path.isdir(os.path.join(folder, item)):
|
if os.path.isdir(os.path.join(folder, item)):
|
||||||
dir_list.append(item)
|
dir_list.append(item)
|
||||||
else:
|
elif str(item) != "crafty.sqlite":
|
||||||
unsorted_files.append(item)
|
unsorted_files.append(item)
|
||||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||||
unsorted_files, key=str.casefold
|
unsorted_files, key=str.casefold
|
||||||
@ -986,14 +939,6 @@ class Helpers:
|
|||||||
[parent_path, child_path]
|
[parent_path, child_path]
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def copy_files(source, dest):
|
|
||||||
if os.path.isfile(source):
|
|
||||||
FileHelpers.copy_file(source, dest)
|
|
||||||
logger.info("Copying jar %s to %s", source, dest)
|
|
||||||
else:
|
|
||||||
logger.info("Source jar does not exist.")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def download_file(executable_url, jar_path):
|
def download_file(executable_url, jar_path):
|
||||||
try:
|
try:
|
||||||
|
@ -34,8 +34,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Controller:
|
class Controller:
|
||||||
def __init__(self, database, helper):
|
def __init__(self, database, helper, file_helper):
|
||||||
self.helper: Helpers = helper
|
self.helper: Helpers = helper
|
||||||
|
self.file_helper: FileHelpers = file_helper
|
||||||
self.server_jars: ServerJars = ServerJars(helper)
|
self.server_jars: ServerJars = ServerJars(helper)
|
||||||
self.users_helper: HelperUsers = HelperUsers(database, self.helper)
|
self.users_helper: HelperUsers = HelperUsers(database, self.helper)
|
||||||
self.roles_helper: HelperRoles = HelperRoles(database)
|
self.roles_helper: HelperRoles = HelperRoles(database)
|
||||||
@ -53,7 +54,7 @@ class Controller:
|
|||||||
)
|
)
|
||||||
self.server_perms: ServerPermsController = ServerPermsController()
|
self.server_perms: ServerPermsController = ServerPermsController()
|
||||||
self.servers: ServersController = ServersController(
|
self.servers: ServersController = ServersController(
|
||||||
self.helper, self.servers_helper, self.management_helper
|
self.helper, self.servers_helper, self.management_helper, self.file_helper
|
||||||
)
|
)
|
||||||
self.users: UsersController = UsersController(
|
self.users: UsersController = UsersController(
|
||||||
self.helper, self.users_helper, self.authentication
|
self.helper, self.users_helper, self.authentication
|
||||||
|
@ -17,8 +17,6 @@ class DatabaseBuilder:
|
|||||||
logger.info("Fresh Install Detected - Creating Default Settings")
|
logger.info("Fresh Install Detected - Creating Default Settings")
|
||||||
Console.info("Fresh Install Detected - Creating Default Settings")
|
Console.info("Fresh Install Detected - Creating Default Settings")
|
||||||
default_data = self.helper.find_default_password()
|
default_data = self.helper.find_default_password()
|
||||||
# Reset this value if the DB has been dumped
|
|
||||||
self.helper.set_setting("apikey_secret", "random")
|
|
||||||
|
|
||||||
username = default_data.get("username", "admin")
|
username = default_data.get("username", "admin")
|
||||||
password = default_data.get("password", "crafty")
|
password = default_data.get("password", "crafty")
|
||||||
|
@ -9,7 +9,6 @@ import threading
|
|||||||
import logging.config
|
import logging.config
|
||||||
import subprocess
|
import subprocess
|
||||||
import html
|
import html
|
||||||
import tempfile
|
|
||||||
|
|
||||||
# TZLocal is set as a hidden import on win pipeline
|
# TZLocal is set as a hidden import on win pipeline
|
||||||
from tzlocal import get_localzone
|
from tzlocal import get_localzone
|
||||||
@ -102,12 +101,14 @@ class ServerOutBuf:
|
|||||||
class ServerInstance:
|
class ServerInstance:
|
||||||
server_object: Servers
|
server_object: Servers
|
||||||
helper: Helpers
|
helper: Helpers
|
||||||
|
file_helper: FileHelpers
|
||||||
management_helper: HelpersManagement
|
management_helper: HelpersManagement
|
||||||
stats: Stats
|
stats: Stats
|
||||||
stats_helper: HelperServerStats
|
stats_helper: HelperServerStats
|
||||||
|
|
||||||
def __init__(self, server_id, helper, management_helper, stats):
|
def __init__(self, server_id, helper, management_helper, stats, file_helper):
|
||||||
self.helper = helper
|
self.helper = helper
|
||||||
|
self.file_helper = file_helper
|
||||||
self.management_helper = management_helper
|
self.management_helper = management_helper
|
||||||
# holders for our process
|
# holders for our process
|
||||||
self.process = None
|
self.process = None
|
||||||
@ -869,62 +870,27 @@ class ServerInstance:
|
|||||||
f" (ID#{self.server_id}, path={self.server_path}) "
|
f" (ID#{self.server_id}, path={self.server_path}) "
|
||||||
f"at '{backup_filename}'"
|
f"at '{backup_filename}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
|
||||||
self.server_scheduler.add_job(
|
|
||||||
self.backup_status,
|
|
||||||
"interval",
|
|
||||||
seconds=1,
|
|
||||||
id="backup_" + str(self.server_id),
|
|
||||||
args=[temp_dir + "/", backup_filename + ".zip"],
|
|
||||||
)
|
|
||||||
# pylint: disable=unexpected-keyword-arg
|
|
||||||
try:
|
|
||||||
FileHelpers.copy_dir(self.server_path, temp_dir, dirs_exist_ok=True)
|
|
||||||
except shutil.Error as e:
|
|
||||||
logger.error(f"Failed to fully complete backup due to shutil error {e}")
|
|
||||||
excluded_dirs = HelpersManagement.get_excluded_backup_dirs(self.server_id)
|
excluded_dirs = HelpersManagement.get_excluded_backup_dirs(self.server_id)
|
||||||
server_dir = Helpers.get_os_understandable_path(self.settings["path"])
|
server_dir = Helpers.get_os_understandable_path(self.settings["path"])
|
||||||
|
|
||||||
for my_dir in excluded_dirs:
|
|
||||||
# Take the full path of the excluded dir and replace the
|
|
||||||
# server path with the temp path, this is so that we're
|
|
||||||
# only deleting excluded dirs from the temp path
|
|
||||||
# and not the server path
|
|
||||||
excluded_dir = Helpers.get_os_understandable_path(my_dir).replace(
|
|
||||||
server_dir, Helpers.get_os_understandable_path(temp_dir)
|
|
||||||
)
|
|
||||||
# Next, check to see if it is a directory
|
|
||||||
if os.path.isdir(excluded_dir):
|
|
||||||
# If it is a directory,
|
|
||||||
# recursively delete the entire directory from the backup
|
|
||||||
try:
|
|
||||||
FileHelpers.del_dirs(excluded_dir)
|
|
||||||
except FileNotFoundError:
|
|
||||||
Console.error(
|
|
||||||
f"Excluded dir {excluded_dir} not found. Moving on..."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# If not, just remove the file
|
|
||||||
try:
|
|
||||||
os.remove(excluded_dir)
|
|
||||||
except:
|
|
||||||
Console.error(
|
|
||||||
f"Excluded dir {excluded_dir} not found. Moving on..."
|
|
||||||
)
|
|
||||||
if conf["compress"]:
|
if conf["compress"]:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Found compress backup to be true. Calling compressed archive"
|
"Found compress backup to be true. Calling compressed archive"
|
||||||
)
|
)
|
||||||
FileHelpers.make_compressed_archive(
|
self.file_helper.make_compressed_backup(
|
||||||
Helpers.get_os_understandable_path(backup_filename), temp_dir
|
Helpers.get_os_understandable_path(backup_filename),
|
||||||
|
server_dir,
|
||||||
|
excluded_dirs,
|
||||||
|
self.server_id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Found compress backup to be false. Calling NON-compressed archive"
|
"Found compress backup to be false. Calling NON-compressed archive"
|
||||||
)
|
)
|
||||||
FileHelpers.make_archive(
|
self.file_helper.make_backup(
|
||||||
Helpers.get_os_understandable_path(backup_filename), temp_dir
|
Helpers.get_os_understandable_path(backup_filename),
|
||||||
|
server_dir,
|
||||||
|
excluded_dirs,
|
||||||
|
self.server_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
while (
|
while (
|
||||||
@ -939,7 +905,6 @@ class ServerInstance:
|
|||||||
|
|
||||||
self.is_backingup = False
|
self.is_backingup = False
|
||||||
logger.info(f"Backup of server: {self.name} completed")
|
logger.info(f"Backup of server: {self.name} completed")
|
||||||
self.server_scheduler.remove_job("backup_" + str(self.server_id))
|
|
||||||
results = {"percent": 100, "total_files": 0, "current_file": 0}
|
results = {"percent": 100, "total_files": 0, "current_file": 0}
|
||||||
if len(self.helper.websocket_helper.clients) > 0:
|
if len(self.helper.websocket_helper.clients) > 0:
|
||||||
self.helper.websocket_helper.broadcast_page_params(
|
self.helper.websocket_helper.broadcast_page_params(
|
||||||
@ -964,7 +929,6 @@ class ServerInstance:
|
|||||||
logger.exception(
|
logger.exception(
|
||||||
f"Failed to create backup of server {self.name} (ID {self.server_id})"
|
f"Failed to create backup of server {self.name} (ID {self.server_id})"
|
||||||
)
|
)
|
||||||
self.server_scheduler.remove_job("backup_" + str(self.server_id))
|
|
||||||
results = {"percent": 100, "total_files": 0, "current_file": 0}
|
results = {"percent": 100, "total_files": 0, "current_file": 0}
|
||||||
if len(self.helper.websocket_helper.clients) > 0:
|
if len(self.helper.websocket_helper.clients) > 0:
|
||||||
self.helper.websocket_helper.broadcast_page_params(
|
self.helper.websocket_helper.broadcast_page_params(
|
||||||
@ -974,8 +938,6 @@ class ServerInstance:
|
|||||||
results,
|
results,
|
||||||
)
|
)
|
||||||
self.is_backingup = False
|
self.is_backingup = False
|
||||||
finally:
|
|
||||||
FileHelpers.del_dirs(temp_dir)
|
|
||||||
|
|
||||||
def backup_status(self, source_path, dest_path):
|
def backup_status(self, source_path, dest_path):
|
||||||
results = Helpers.calc_percent(source_path, dest_path)
|
results = Helpers.calc_percent(source_path, dest_path)
|
||||||
@ -1093,7 +1055,7 @@ class ServerInstance:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# copies to backup dir
|
# copies to backup dir
|
||||||
Helpers.copy_files(current_executable, backup_executable)
|
FileHelpers.copy_file(current_executable, backup_executable)
|
||||||
|
|
||||||
# boolean returns true for false for success
|
# boolean returns true for false for success
|
||||||
downloaded = Helpers.download_file(
|
downloaded = Helpers.download_file(
|
||||||
|
@ -220,7 +220,7 @@ class FileHandler(BaseHandler):
|
|||||||
path = Helpers.get_os_understandable_path(self.get_argument("path", None))
|
path = Helpers.get_os_understandable_path(self.get_argument("path", None))
|
||||||
if Helpers.is_os_windows():
|
if Helpers.is_os_windows():
|
||||||
path = Helpers.wtol_path(path)
|
path = Helpers.wtol_path(path)
|
||||||
Helpers.unzip_file(path)
|
FileHelpers.unzip_file(path)
|
||||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
|
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1342,6 +1342,8 @@ class PanelHandler(BaseHandler):
|
|||||||
if Helpers.is_os_windows():
|
if Helpers.is_os_windows():
|
||||||
log_path.replace(" ", "^ ")
|
log_path.replace(" ", "^ ")
|
||||||
log_path = Helpers.wtol_path(log_path)
|
log_path = Helpers.wtol_path(log_path)
|
||||||
|
if not self.helper.validate_traversal(server_obj.path, log_path):
|
||||||
|
log_path = ""
|
||||||
executable = self.get_argument("executable", None)
|
executable = self.get_argument("executable", None)
|
||||||
execution_command = self.get_argument("execution_command", None)
|
execution_command = self.get_argument("execution_command", None)
|
||||||
server_ip = self.get_argument("server_ip", None)
|
server_ip = self.get_argument("server_ip", None)
|
||||||
@ -1941,7 +1943,10 @@ class PanelHandler(BaseHandler):
|
|||||||
self.redirect("/panel/error?error=Invalid Key ID")
|
self.redirect("/panel/error?error=Invalid Key ID")
|
||||||
return
|
return
|
||||||
|
|
||||||
if key.user_id != exec_user["user_id"]:
|
if (
|
||||||
|
str(key.user_id) != str(exec_user["user_id"])
|
||||||
|
and not exec_user["superuser"]
|
||||||
|
):
|
||||||
self.redirect(
|
self.redirect(
|
||||||
"/panel/error?error=You are not authorized to access this key."
|
"/panel/error?error=You are not authorized to access this key."
|
||||||
)
|
)
|
||||||
|
@ -271,6 +271,14 @@ class ServerHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if page == "step1":
|
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:
|
if not superuser:
|
||||||
user_roles = self.controller.roles.get_all_roles()
|
user_roles = self.controller.roles.get_all_roles()
|
||||||
@ -396,6 +404,14 @@ class ServerHandler(BaseHandler):
|
|||||||
self.redirect("/panel/dashboard")
|
self.redirect("/panel/dashboard")
|
||||||
|
|
||||||
if page == "bedrock_step1":
|
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:
|
if not superuser:
|
||||||
user_roles = self.controller.roles.get_all_roles()
|
user_roles = self.controller.roles.get_all_roles()
|
||||||
else:
|
else:
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h4 class="page-title">
|
<h4 class="page-title">
|
||||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
|
||||||
|
data['server_stats']['server_id']['server_name'] }}
|
||||||
<br />
|
<br />
|
||||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||||
</h4>
|
</h4>
|
||||||
@ -51,55 +52,75 @@
|
|||||||
|
|
||||||
{% 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" role="progressbar" style="width:{{data['backup_stats']['percent']}}%;" aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{ data['backup_stats']['percent'] }}%</div>
|
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
|
||||||
|
role="progressbar" style="width:{{data['backup_stats']['percent']}}%;"
|
||||||
|
aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{
|
||||||
|
data['backup_stats']['percent'] }}%</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Backing up <span id="total_files">{{data['backup_stats']['total_files']}}</span> Files</p>
|
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
|
||||||
|
id="total_files">{{data['server_stats']['world_size']}}</span></p>
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
{% if not data['backing_up'] %}
|
{% if not data['backing_up'] %}
|
||||||
<div id="backup_button" class="form-group">
|
<div id="backup_button" class="form-group">
|
||||||
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow', data['lang']) }}</button>
|
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow',
|
||||||
|
data['lang']) }}</button>
|
||||||
</div>
|
</div>
|
||||||
{% end %}
|
{% end %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{% if data['super_user'] %}
|
{% if data['super_user'] %}
|
||||||
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang']) }}</small> </label>
|
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small
|
||||||
<input type="text" class="form-control" name="backup_path" id="backup_path" value="{{ data['server_stats']['server_id']['backup_path'] }}" placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}">
|
class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang'])
|
||||||
|
}}</small> </label>
|
||||||
|
<input type="text" class="form-control" name="backup_path" id="backup_path"
|
||||||
|
value="{{ data['server_stats']['server_id']['backup_path'] }}"
|
||||||
|
placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}">
|
||||||
{% end %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang']) }}</small> </label>
|
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small
|
||||||
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
|
class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang'])
|
||||||
|
}}</small> </label>
|
||||||
|
<input type="text" class="form-control" name="max_backups" id="max_backups"
|
||||||
|
value="{{ data['backup_config']['max_backups'] }}"
|
||||||
|
placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="compress" class="form-check-label ml-4 mb-4"></label>
|
<label for="compress" class="form-check-label ml-4 mb-4"></label>
|
||||||
{% if data['backup_config']['compress'] %}
|
{% if data['backup_config']['compress'] %}
|
||||||
<input type="checkbox" class="form-check-input" id="compress" name="compress"
|
<input type="checkbox" class="form-check-input" id="compress" name="compress" checked=""
|
||||||
checked="" value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
|
value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="checkbox" class="form-check-input" id="compress" name="compress"
|
<input type="checkbox" class="form-check-input" id="compress" name="compress" value="True">{{
|
||||||
value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
|
translate('serverBackups', 'compress', data['lang']) }}
|
||||||
{% end %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{ translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
|
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{
|
||||||
|
translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
|
||||||
<br>
|
<br>
|
||||||
<button class="btn btn-primary mr-2" id="root_files_button" data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{ translate('serverBackups', 'clickExclude', data['lang']) }}</button>
|
<button class="btn btn-primary mr-2" id="root_files_button"
|
||||||
|
data-server_path="{{ data['server_stats']['server_id']['path']}}" type="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>
|
<input type="number" class="form-control" name="changed" id="changed" value="0"
|
||||||
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
|
style="visibility: hidden;"></input>
|
||||||
|
<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">
|
||||||
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups', 'excludedChoose', data['lang']) }}</h5>
|
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups',
|
||||||
|
'excludedChoose', data['lang']) }}</h5>
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</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=""
|
||||||
|
style="overflow: scroll; max-height:75%;">
|
||||||
<input type="checkbox" id="main-tree-input" name="root_path" value="" disabled>
|
<input type="checkbox" id="main-tree-input" name="root_path" value="" disabled>
|
||||||
<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>
|
||||||
@ -110,15 +131,19 @@
|
|||||||
</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">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
|
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{
|
||||||
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button>
|
translate('serverBackups', 'cancel', data['lang']) }}</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang']) }}</button>
|
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang'])
|
||||||
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
|
}}</button>
|
||||||
|
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang'])
|
||||||
|
}}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -138,13 +163,15 @@
|
|||||||
{% for backup in data['backup_list'] %}
|
{% for backup in data['backup_list'] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary">
|
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}"
|
||||||
|
class="btn btn-primary">
|
||||||
<i class="fas fa-download" aria-hidden="true"></i>
|
<i class="fas fa-download" aria-hidden="true"></i>
|
||||||
{{ translate('serverBackups', 'download', data['lang']) }}
|
{{ translate('serverBackups', 'download', data['lang']) }}
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}" class="btn btn-danger del_button">
|
<button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}"
|
||||||
|
class="btn btn-danger del_button">
|
||||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||||
{{ translate('serverBackups', 'delete', data['lang']) }}
|
{{ translate('serverBackups', 'delete', data['lang']) }}
|
||||||
</button>
|
</button>
|
||||||
@ -168,7 +195,8 @@
|
|||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<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">
|
||||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups', data['lang']) }} <small class="text-muted ml-1"></small> </h4>
|
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups',
|
||||||
|
data['lang']) }} <small class="text-muted ml-1"></small> </h4>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<ul>
|
<ul>
|
||||||
|
16
app/migrations/20220618_crafty_api_secret.py
Normal file
16
app/migrations/20220618_crafty_api_secret.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import peewee
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(migrator, db):
|
||||||
|
class CraftySettings(peewee.Model):
|
||||||
|
secret_api_key = peewee.CharField(default="")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
table_name = "crafty_settings"
|
||||||
|
|
||||||
|
migrator.create_table(CraftySettings)
|
||||||
|
|
||||||
|
|
||||||
|
def rollback(migrator, db):
|
||||||
|
migrator.drop_table("crafty_settings")
|
5
main.py
5
main.py
@ -7,6 +7,7 @@ import argparse
|
|||||||
import logging.config
|
import logging.config
|
||||||
import signal
|
import signal
|
||||||
import peewee
|
import peewee
|
||||||
|
from app.classes.shared.file_helpers import FileHelpers
|
||||||
|
|
||||||
from app.classes.shared.import3 import Import3
|
from app.classes.shared.import3 import Import3
|
||||||
from app.classes.shared.console import Console
|
from app.classes.shared.console import Console
|
||||||
@ -132,9 +133,9 @@ if __name__ == "__main__":
|
|||||||
installer.default_settings()
|
installer.default_settings()
|
||||||
else:
|
else:
|
||||||
Console.debug("Existing install detected")
|
Console.debug("Existing install detected")
|
||||||
|
file_helper = FileHelpers(helper)
|
||||||
# now the tables are created, we can load the tasks_manager and server controller
|
# now the tables are created, we can load the tasks_manager and server controller
|
||||||
controller = Controller(database, helper)
|
controller = Controller(database, helper, file_helper)
|
||||||
import3 = Import3(helper, controller)
|
import3 = Import3(helper, controller)
|
||||||
tasks_manager = TasksManager(helper, controller)
|
tasks_manager = TasksManager(helper, controller)
|
||||||
tasks_manager.start_webserver()
|
tasks_manager.start_webserver()
|
||||||
|
Loading…
Reference in New Issue
Block a user