Merge branch 'dev' into tweak/master-server-dir-config

This commit is contained in:
amcmanu3 2023-01-30 12:14:58 -05:00
commit 3a763178d7
39 changed files with 1711 additions and 643 deletions

View File

@ -1,5 +1,5 @@
# Changelog # Changelog
## --- [4.0.20] - 2022/TBD ## --- [4.0.21] - 2023/TBD
### New features ### New features
TBD TBD
### Bug fixes ### Bug fixes
@ -10,7 +10,27 @@ TBD
TBD TBD
<br><br> <br><br>
## --- [4.0.19] - 2022/01/07 ## --- [4.0.20] - 2023/01/29
### New features
- Add option to run command before backup. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/536))
- Make Config.json editable from panel. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/532))
- Managed config.json refector (See MR for details). ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/485))
### Bug fixes
- Fix local java server imports. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/529))
- Fix Schedule Restore | Add Backup Config Preservation. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/533))
- Rework `/public` Route. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/538))
### Tweaks
- Hide stats DB directory from files tree. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/530))
- Make it so file tree doesn't reload on upload/delete. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/541))
- Add upload completed feedback to file upload. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/541))
- Added further login screen customisation settings. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/531))
- Set backup filename to use same time as schedule. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/534))
- Move Schedules to from DB to Queue Datatype. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/535))
- Move raknet icon failure to a debug log. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/537))
- Add Default redirection to Dashboard if the user is connected. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/540))
<br><br>
## --- [4.0.19] - 2023/01/07
### Bug fixes ### Bug fixes
- Fix port tooltip not showing on dash while server online. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/503)) - Fix port tooltip not showing on dash while server online. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/503))
- Fix '+' char in path causing any file operation to fail. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/502)) - Fix '+' char in path causing any file operation to fail. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/502))

View File

@ -1,5 +1,5 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com) [![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
# Crafty Controller 4.0.20 # Crafty Controller 4.0.21
> Python based Control Panel for your Minecraft Server > Python based Control Panel for your Minecraft Server
## What is Crafty Controller? ## What is Crafty Controller?

View File

@ -1,4 +1,5 @@
import logging import logging
import queue
from app.classes.models.management import HelpersManagement from app.classes.models.management import HelpersManagement
from app.classes.models.servers import HelperServers from app.classes.models.servers import HelperServers
@ -9,6 +10,26 @@ logger = logging.getLogger(__name__)
class ManagementController: class ManagementController:
def __init__(self, management_helper): def __init__(self, management_helper):
self.management_helper = management_helper self.management_helper = management_helper
self.command_queue = queue.Queue()
# **********************************************************************************
# Config Methods
# **********************************************************************************
@staticmethod
def set_login_image(path):
HelpersManagement.set_login_image(path)
@staticmethod
def get_login_image():
return HelpersManagement.get_login_image()
@staticmethod
def set_login_opacity(opacity):
return HelpersManagement.set_login_opacity(opacity)
@staticmethod
def get_login_opacity():
return HelpersManagement.get_login_opacity()
# ********************************************************************************** # **********************************************************************************
# Host_Stats Methods # Host_Stats Methods
@ -25,12 +46,17 @@ class ManagementController:
def get_crafty_api_key(): def get_crafty_api_key():
return HelpersManagement.get_secret_api_key() return HelpersManagement.get_secret_api_key()
@staticmethod
def set_cookie_secret(key):
HelpersManagement.set_cookie_secret(key)
@staticmethod
def add_crafty_row():
HelpersManagement.create_crafty_row()
# ********************************************************************************** # **********************************************************************************
# Commands Methods # Commands Methods
# ********************************************************************************** # **********************************************************************************
@staticmethod
def get_unactioned_commands():
return HelpersManagement.get_unactioned_commands()
def send_command(self, user_id, server_id, remote_ip, command): def send_command(self, user_id, server_id, remote_ip, command):
server_name = HelperServers.get_server_friendly_name(server_id) server_name = HelperServers.get_server_friendly_name(server_id)
@ -42,11 +68,12 @@ class ManagementController:
server_id, server_id,
remote_ip, remote_ip,
) )
HelpersManagement.add_command(server_id, user_id, remote_ip, command) self.queue_command(
{"server_id": server_id, "user_id": user_id, "command": command}
)
@staticmethod def queue_command(self, command_data):
def mark_command_complete(command_id=None): self.command_queue.put(command_data)
return HelpersManagement.mark_command_complete(command_id)
# ********************************************************************************** # **********************************************************************************
# Audit_Log Methods # Audit_Log Methods
@ -78,6 +105,10 @@ class ManagementController:
command, command,
name, name,
enabled=True, enabled=True,
one_time=False,
cron_string="* * * * *",
parent=None,
delay=0,
): ):
return HelpersManagement.create_scheduled_task( return HelpersManagement.create_scheduled_task(
server_id, server_id,
@ -88,20 +119,16 @@ class ManagementController:
command, command,
name, name,
enabled, enabled,
one_time,
cron_string,
parent,
delay,
) )
@staticmethod @staticmethod
def delete_scheduled_task(schedule_id): def delete_scheduled_task(schedule_id):
return HelpersManagement.delete_scheduled_task(schedule_id) return HelpersManagement.delete_scheduled_task(schedule_id)
@staticmethod
def set_login_image(path):
HelpersManagement.set_login_image(path)
@staticmethod
def get_login_image():
return HelpersManagement.get_login_image()
@staticmethod @staticmethod
def update_scheduled_task(schedule_id, updates): def update_scheduled_task(schedule_id, updates):
return HelpersManagement.update_scheduled_task(schedule_id, updates) return HelpersManagement.update_scheduled_task(schedule_id, updates)
@ -145,9 +172,18 @@ class ManagementController:
excluded_dirs: list = None, excluded_dirs: list = None,
compress: bool = False, compress: bool = False,
shutdown: bool = False, shutdown: bool = False,
before: str = "",
after: str = "",
): ):
return self.management_helper.set_backup_config( return self.management_helper.set_backup_config(
server_id, backup_path, max_backups, excluded_dirs, compress, shutdown server_id,
backup_path,
max_backups,
excluded_dirs,
compress,
shutdown,
before,
after,
) )
@staticmethod @staticmethod

View File

@ -300,7 +300,7 @@ class Stats:
server_icon = base64.encodebytes(ping_obj["icon"]) server_icon = base64.encodebytes(ping_obj["icon"])
except Exception as e: except Exception as e:
server_icon = False server_icon = False
logger.info( logger.debug(
"Unable to read the server icon due to the following error:", exc_info=e "Unable to read the server icon due to the following error:", exc_info=e
) )
ping_data = { ping_data = {

View File

@ -13,7 +13,7 @@ from peewee import (
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
from app.classes.models.base_model import BaseModel from app.classes.models.base_model import BaseModel
from app.classes.models.users import Users, HelperUsers from app.classes.models.users import HelperUsers
from app.classes.models.servers import Servers from app.classes.models.servers import Servers
from app.classes.models.server_permissions import PermissionsServers from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.main_models import DatabaseShortcuts from app.classes.shared.main_models import DatabaseShortcuts
@ -43,7 +43,9 @@ class AuditLog(BaseModel):
# ********************************************************************************** # **********************************************************************************
class CraftySettings(BaseModel): class CraftySettings(BaseModel):
secret_api_key = CharField(default="") secret_api_key = CharField(default="")
cookie_secret = CharField(default="")
login_photo = CharField(default="login_1.jpg") login_photo = CharField(default="login_1.jpg")
login_opacity = IntegerField(default=100)
master_server_dir = CharField(default="") master_server_dir = CharField(default="")
class Meta: class Meta:
@ -69,22 +71,6 @@ class HostStats(BaseModel):
table_name = "host_stats" table_name = "host_stats"
# **********************************************************************************
# Commands Class
# **********************************************************************************
class Commands(BaseModel):
command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref="server", index=True)
user = ForeignKeyField(Users, backref="user", index=True)
source_ip = CharField(default="127.0.0.1")
command = CharField(default="")
executed = BooleanField(default=False)
class Meta:
table_name = "commands"
# ********************************************************************************** # **********************************************************************************
# Webhooks Class # Webhooks Class
# ********************************************************************************** # **********************************************************************************
@ -132,6 +118,8 @@ class Backups(BaseModel):
server_id = ForeignKeyField(Servers, backref="backups_server") server_id = ForeignKeyField(Servers, backref="backups_server")
compress = BooleanField(default=False) compress = BooleanField(default=False)
shutdown = BooleanField(default=False) shutdown = BooleanField(default=False)
before = CharField(default="")
after = CharField(default="")
class Meta: class Meta:
table_name = "backups" table_name = "backups"
@ -151,33 +139,6 @@ class HelpersManagement:
query = HostStats.select().order_by(HostStats.id.desc()).get() query = HostStats.select().order_by(HostStats.id.desc()).get()
return model_to_dict(query) return model_to_dict(query)
# **********************************************************************************
# Commands Methods
# **********************************************************************************
@staticmethod
def add_command(server_id, user_id, remote_ip, command):
Commands.insert(
{
Commands.server_id: server_id,
Commands.user: user_id,
Commands.source_ip: remote_ip,
Commands.command: command,
}
).execute()
@staticmethod
def get_unactioned_commands():
query = Commands.select().where(Commands.executed == 0)
return query
@staticmethod
def mark_command_complete(command_id=None):
if command_id is not None:
logger.debug(f"Marking Command {command_id} completed")
Commands.update({Commands.executed: True}).where(
Commands.command_id == command_id
).execute()
# ********************************************************************************** # **********************************************************************************
# Audit_Log Methods # Audit_Log Methods
# ********************************************************************************** # **********************************************************************************
@ -245,9 +206,22 @@ class HelpersManagement:
else: else:
return return
@staticmethod
def create_crafty_row():
CraftySettings.insert(
{
CraftySettings.secret_api_key: "",
CraftySettings.cookie_secret: "",
CraftySettings.login_photo: "login_1.jpg",
CraftySettings.login_opacity: 100,
}
).execute()
@staticmethod @staticmethod
def set_secret_api_key(key): def set_secret_api_key(key):
CraftySettings.insert(secret_api_key=key).execute() CraftySettings.update({CraftySettings.secret_api_key: key}).where(
CraftySettings.id == 1
).execute()
@staticmethod @staticmethod
def get_secret_api_key(): def get_secret_api_key():
@ -256,6 +230,22 @@ class HelpersManagement:
) )
return settings[0].secret_api_key return settings[0].secret_api_key
@staticmethod
def get_cookie_secret():
settings = CraftySettings.select(CraftySettings.cookie_secret).where(
CraftySettings.id == 1
)
return settings[0].cookie_secret
@staticmethod
def set_cookie_secret(key):
CraftySettings.update({CraftySettings.cookie_secret: key}).where(
CraftySettings.id == 1
).execute()
# **********************************************************************************
# Config Methods
# **********************************************************************************
@staticmethod @staticmethod
def get_login_image(): def get_login_image():
settings = CraftySettings.select(CraftySettings.login_photo).where( settings = CraftySettings.select(CraftySettings.login_photo).where(
@ -269,6 +259,19 @@ class HelpersManagement:
CraftySettings.id == 1 CraftySettings.id == 1
).execute() ).execute()
@staticmethod
def get_login_opacity():
settings = CraftySettings.select(CraftySettings.login_opacity).where(
CraftySettings.id == 1
)
return settings[0].login_opacity
@staticmethod
def set_login_opacity(opacity):
CraftySettings.update({CraftySettings.login_opacity: opacity}).where(
CraftySettings.id == 1
).execute()
@staticmethod @staticmethod
def get_master_server_dir(): def get_master_server_dir():
settings = CraftySettings.select(CraftySettings.master_server_dir).where( settings = CraftySettings.select(CraftySettings.master_server_dir).where(
@ -383,6 +386,8 @@ class HelpersManagement:
"server_id": row.server_id_id, "server_id": row.server_id_id,
"compress": row.compress, "compress": row.compress,
"shutdown": row.shutdown, "shutdown": row.shutdown,
"before": row.before,
"after": row.after,
} }
except IndexError: except IndexError:
conf = { conf = {
@ -392,6 +397,8 @@ class HelpersManagement:
"server_id": server_id, "server_id": server_id,
"compress": False, "compress": False,
"shutdown": False, "shutdown": False,
"before": "",
"after": "",
} }
return conf return conf
@ -407,6 +414,8 @@ class HelpersManagement:
excluded_dirs: list = None, excluded_dirs: list = None,
compress: bool = False, compress: bool = False,
shutdown: bool = False, shutdown: bool = False,
before: str = "",
after: str = "",
): ):
logger.debug(f"Updating server {server_id} backup config with {locals()}") logger.debug(f"Updating server {server_id} backup config with {locals()}")
if Backups.select().where(Backups.server_id == server_id).exists(): if Backups.select().where(Backups.server_id == server_id).exists():
@ -419,6 +428,8 @@ class HelpersManagement:
"server_id": server_id, "server_id": server_id,
"compress": False, "compress": False,
"shutdown": False, "shutdown": False,
"before": "",
"after": "",
} }
new_row = True new_row = True
if max_backups is not None: if max_backups is not None:
@ -428,6 +439,8 @@ class HelpersManagement:
conf["excluded_dirs"] = dirs_to_exclude conf["excluded_dirs"] = dirs_to_exclude
conf["compress"] = compress conf["compress"] = compress
conf["shutdown"] = shutdown conf["shutdown"] = shutdown
conf["before"] = before
conf["after"] = after
if not new_row: if not new_row:
with self.database.atomic(): with self.database.atomic():
if backup_path is not None: if backup_path is not None:
@ -487,9 +500,3 @@ class HelpersManagement:
f"Not removing {dir_to_del} from excluded directories - " f"Not removing {dir_to_del} from excluded directories - "
f"not in the excluded directory list for server ID {server_id}" f"not in the excluded directory list for server ID {server_id}"
) )
@staticmethod
def clear_unexecuted_commands():
Commands.update({Commands.executed: True}).where(
Commands.executed == False # pylint: disable=singleton-comparison
).execute()

View File

@ -78,6 +78,7 @@ class Helpers:
self.websocket_helper = WebSocketHelper(self) self.websocket_helper = WebSocketHelper(self)
self.translation = Translation(self) self.translation = Translation(self)
self.update_available = False self.update_available = False
self.ignored_names = ["crafty_managed.txt", "db_stats"]
@staticmethod @staticmethod
def auto_installer_fix(ex): def auto_installer_fix(ex):
@ -376,6 +377,64 @@ class Helpers:
return default_return return default_return
def set_settings(self, data):
try:
with open(self.settings_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
except Exception as e:
logger.critical(
f"Config File Error: Unable to read {self.settings_file} due to {e}"
)
Console.critical(
f"Config File Error: Unable to read {self.settings_file} due to {e}"
)
return False
return True
@staticmethod
def get_master_config():
# Make changes for users' local config.json files here. As of 4.0.20
# Config.json was removed from the repo to make it easier for users
# To make non-breaking changes to the file.
return {
"http_port": 8000,
"https_port": 8443,
"language": "en_EN",
"cookie_expire": 30,
"show_errors": True,
"history_max_age": 7,
"stats_update_frequency": 30,
"delete_default_json": False,
"show_contribute_link": True,
"virtual_terminal_lines": 70,
"max_log_lines": 700,
"max_audit_entries": 300,
"disabled_language_files": [],
"stream_size_GB": 1,
"keywords": ["help", "chunk"],
"allow_nsfw_profile_pictures": False,
"enable_user_self_delete": False,
"reset_secrets_on_next_boot": False,
}
def get_all_settings(self):
try:
with open(self.settings_file, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
data = {}
logger.critical(
f"Config File Error: Unable to read {self.settings_file} due to {e}"
)
Console.critical(
f"Config File Error: Unable to read {self.settings_file} due to {e}"
)
return data
@staticmethod @staticmethod
def is_subdir(server_path, root_dir): def is_subdir(server_path, root_dir):
server_path = os.path.realpath(server_path) server_path = os.path.realpath(server_path)
@ -947,8 +1006,7 @@ class Helpers:
return data return data
@staticmethod def generate_tree(self, folder, output=""):
def generate_tree(folder, output=""):
dir_list = [] dir_list = []
unsorted_files = [] unsorted_files = []
file_list = os.listdir(folder) file_list = os.listdir(folder)
@ -965,9 +1023,13 @@ class Helpers:
rel = os.path.join(folder, raw_filename) rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename) dpath = os.path.join(folder, filename)
if os.path.isdir(rel): if os.path.isdir(rel):
output += f"""<li class="tree-item" data-path="{dpath}"> if filename not in self.ignored_names:
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder"> output += f"""<li id="{dpath}li" class="tree-item"
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)"> 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"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i> <i style="color: var(--info);" class="far fa-folder-open"></i>
{filename} {filename}
@ -975,8 +1037,8 @@ class Helpers:
</div><li> </div><li>
\n""" \n"""
else: else:
if filename != "crafty_managed.txt": if filename not in self.ignored_names:
output += f"""<li output += f"""<li id="{dpath}li"
class="d-block tree-ctx-item tree-file tree-item" class="d-block tree-ctx-item tree-file tree-item"
data-path="{dpath}" data-path="{dpath}"
data-name="{filename}" data-name="{filename}"
@ -984,8 +1046,7 @@ class Helpers:
<i class="far fa-file"></i></span>{filename}</li>""" <i class="far fa-file"></i></span>{filename}</li>"""
return output return output
@staticmethod def generate_dir(self, folder, output=""):
def generate_dir(folder, output=""):
dir_list = [] dir_list = []
unsorted_files = [] unsorted_files = []
@ -1004,17 +1065,21 @@ class Helpers:
dpath = os.path.join(folder, filename) dpath = os.path.join(folder, filename)
rel = os.path.join(folder, raw_filename) rel = os.path.join(folder, raw_filename)
if os.path.isdir(rel): if os.path.isdir(rel):
output += f"""<li class="tree-item" data-path="{dpath}"> if filename not in self.ignored_names:
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder"> output += f"""<li id="{dpath}li" class="tree-item"
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)"> 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"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i> <i style="color: var(--info);" class="far fa-folder-open"></i>
{filename} {filename}
</span> </span>
</div><li>""" </div><li>"""
else: else:
if filename != "crafty_managed.txt": if filename not in self.ignored_names:
output += f"""<li output += f"""<li id="{dpath}li"
class="d-block tree-ctx-item tree-file tree-item" class="d-block tree-ctx-item tree-file tree-item"
data-path="{dpath}" data-path="{dpath}"
data-name="{filename}" data-name="{filename}"

View File

@ -244,6 +244,41 @@ class Controller:
exec_user["user_id"], "support_status_update", results exec_user["user_id"], "support_status_update", results
) )
def get_config_diff(self):
master_config = Helpers.get_master_config()
try:
user_config = self.helper.get_all_settings()
except:
# Call helper to set updated config.
Console.warning("No Config found. Setting Default Config.json")
user_config = master_config
keys = list(user_config.keys())
keys.sort()
sorted_data = {i: user_config[i] for i in keys}
self.helper.set_settings(user_config)
return
items_to_del = []
# Iterate through user's config.json and check for
# Keys/values that need to be removed
for key in user_config:
if key not in master_config.keys():
items_to_del.append(key)
# Remove key/values from user config that were staged
for item in items_to_del[:]:
del user_config[item]
# Add new keys to user config.
for key, value in master_config.items():
if key not in user_config.keys():
user_config[key] = value
# Call helper to set updated config.
keys = list(user_config.keys())
keys.sort()
sorted_data = {i: user_config[i] for i in keys}
self.helper.set_settings(sorted_data)
def send_log_status(self): def send_log_status(self):
try: try:
return self.log_stats return self.log_stats
@ -965,10 +1000,6 @@ class Controller:
# remove the server from the DB # remove the server from the DB
self.servers.remove_server(server_id) self.servers.remove_server(server_id)
@staticmethod
def clear_unexecuted_commands():
HelpersManagement.clear_unexecuted_commands()
@staticmethod @staticmethod
def clear_support_status(): def clear_support_status():
HelperUsers.clear_support_status() HelperUsers.clear_support_status()

View File

@ -8,9 +8,10 @@ logger = logging.getLogger(__name__)
class DatabaseBuilder: class DatabaseBuilder:
def __init__(self, database, helper, users_helper): def __init__(self, database, helper, users_helper, management_helper):
self.database = database self.database = database
self.helper = helper self.helper = helper
self.management_helper = management_helper
self.users_helper = users_helper self.users_helper = users_helper
def default_settings(self): def default_settings(self):
@ -29,6 +30,8 @@ class DatabaseBuilder:
manager=None, manager=None,
) )
self.management_helper.create_crafty_row()
def is_fresh_install(self): def is_fresh_install(self):
try: try:
num_user = self.users_helper.get_user_total() num_user = self.users_helper.get_user_total()

View File

@ -131,13 +131,13 @@ class ServerInstance:
self.stats_helper = HelperServerStats(self.server_id) self.stats_helper = HelperServerStats(self.server_id)
self.last_backup_failed = False self.last_backup_failed = False
try: try:
tz = get_localzone() self.tz = get_localzone()
except ZoneInfoNotFoundError: except ZoneInfoNotFoundError:
logger.error( logger.error(
"Could not capture time zone from system. Falling back to Europe/London" "Could not capture time zone from system. Falling back to Europe/London"
) )
tz = "Europe/London" self.tz = "Europe/London"
self.server_scheduler = BackgroundScheduler(timezone=str(tz)) self.server_scheduler = BackgroundScheduler(timezone=str(self.tz))
self.server_scheduler.start() self.server_scheduler.start()
self.backup_thread = threading.Thread( self.backup_thread = threading.Thread(
target=self.a_backup_server, daemon=True, name=f"backup_{self.name}" target=self.a_backup_server, daemon=True, name=f"backup_{self.name}"
@ -175,7 +175,6 @@ class ServerInstance:
self.name = server_name self.name = server_name
self.settings = server_data_obj self.settings = server_data_obj
self.stats_helper.init_database(server_id)
self.record_server_stats() self.record_server_stats()
# build our server run command # build our server run command
@ -1024,7 +1023,17 @@ class ServerInstance:
) )
time.sleep(3) time.sleep(3)
conf = HelpersManagement.get_backup_config(self.server_id) conf = HelpersManagement.get_backup_config(self.server_id)
if conf["before"]:
if self.check_running():
logger.debug(
"Found running server and send command option. Sending command"
)
self.send_command(conf["before"])
if conf["shutdown"]: if conf["shutdown"]:
if conf["before"]:
# pause to let people read message.
time.sleep(5)
logger.info( logger.info(
"Found shutdown preference. Delaying" "Found shutdown preference. Delaying"
+ "backup start. Shutting down server." + "backup start. Shutting down server."
@ -1037,7 +1046,7 @@ class ServerInstance:
try: try:
backup_filename = ( backup_filename = (
f"{self.settings['backup_path']}/" f"{self.settings['backup_path']}/"
f"{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}" f"{datetime.datetime.now().astimezone(self.tz).strftime('%Y-%m-%d_%H-%M-%S')}" # pylint: disable=line-too-long
) )
logger.info( logger.info(
f"Creating backup of server '{self.settings['server_name']}'" f"Creating backup of server '{self.settings['server_name']}'"
@ -1105,6 +1114,14 @@ class ServerInstance:
self.run_threaded_server(HelperUsers.get_user_id_by_name("system")) self.run_threaded_server(HelperUsers.get_user_id_by_name("system"))
time.sleep(3) time.sleep(3)
self.last_backup_failed = False self.last_backup_failed = False
if conf["after"]:
if self.check_running():
logger.debug(
"Found running server and send command option. Sending command"
)
self.send_command(conf["after"])
# pause to let people read message.
time.sleep(5)
except: except:
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})"

View File

@ -91,22 +91,21 @@ class TasksManager:
def command_watcher(self): def command_watcher(self):
while True: while True:
# select any commands waiting to be processed # select any commands waiting to be processed
commands = HelpersManagement.get_unactioned_commands() if not self.controller.management.command_queue.empty():
for cmd in commands: cmd = self.controller.management.command_queue.get()
try: try:
svr = self.controller.servers.get_server_instance_by_id( svr = self.controller.servers.get_server_instance_by_id(
cmd.server_id.server_id cmd["server_id"]
) )
except: except:
logger.error( logger.error(
"Server value requested does not exist! " "Server value requested does not exist! "
"Purging item from waiting commands." "Purging item from waiting commands."
) )
HelpersManagement.mark_command_complete(cmd.command_id)
continue continue
user_id = cmd.user_id user_id = cmd["user_id"]
command = cmd.command command = cmd["command"]
if command == "start_server": if command == "start_server":
svr.run_threaded_server(user_id) svr.run_threaded_server(user_id)
@ -136,8 +135,6 @@ class TasksManager:
else: else:
svr.send_command(command) svr.send_command(command)
HelpersManagement.mark_command_complete(cmd.command_id)
time.sleep(1) time.sleep(1)
def _main_graceful_exit(self): def _main_graceful_exit(self):
@ -212,16 +209,19 @@ class TasksManager:
if schedule.cron_string != "": if schedule.cron_string != "":
try: try:
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
CronTrigger.from_crontab( CronTrigger.from_crontab(
schedule.cron_string, timezone=str(self.tz) schedule.cron_string, timezone=str(self.tz)
), ),
id=str(schedule.schedule_id), id=str(schedule.schedule_id),
args=[ args=[
schedule.server_id, {
self.users_controller.get_id_by_name("system"), "server_id": schedule.server_id.server_id,
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
schedule.command, "system"
),
"command": schedule.command,
}
], ],
) )
except Exception as e: except Exception as e:
@ -237,45 +237,54 @@ class TasksManager:
else: else:
if schedule.interval_type == "hours": if schedule.interval_type == "hours":
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
minute=0, minute=0,
hour="*/" + str(schedule.interval), hour="*/" + str(schedule.interval),
id=str(schedule.schedule_id), id=str(schedule.schedule_id),
args=[ args=[
schedule.server_id, {
self.users_controller.get_id_by_name("system"), "server_id": schedule.server_id.server_id,
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
schedule.command, "system"
),
"command": schedule.command,
}
], ],
) )
elif schedule.interval_type == "minutes": elif schedule.interval_type == "minutes":
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
minute="*/" + str(schedule.interval), minute="*/" + str(schedule.interval),
id=str(schedule.schedule_id), id=str(schedule.schedule_id),
args=[ args=[
schedule.server_id, {
self.users_controller.get_id_by_name("system"), "server_id": schedule.server_id.server_id,
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
schedule.command, "system"
),
"command": schedule.command,
}
], ],
) )
elif schedule.interval_type == "days": elif schedule.interval_type == "days":
curr_time = schedule.start_time.split(":") curr_time = schedule.start_time.split(":")
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
day="*/" + str(schedule.interval), day="*/" + str(schedule.interval),
hour=curr_time[0], hour=curr_time[0],
minute=curr_time[1], minute=curr_time[1],
id=str(schedule.schedule_id), id=str(schedule.schedule_id),
args=[ args=[
schedule.server_id, {
self.users_controller.get_id_by_name("system"), "server_id": schedule.server_id.server_id,
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
schedule.command, "system"
),
"command": schedule.command,
}
], ],
) )
if new_job != "error": if new_job != "error":
@ -322,16 +331,19 @@ class TasksManager:
if job_data["cron_string"] != "": if job_data["cron_string"] != "":
try: try:
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
CronTrigger.from_crontab( CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz) job_data["cron_string"], timezone=str(self.tz)
), ),
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
except Exception as e: except Exception as e:
@ -345,45 +357,54 @@ class TasksManager:
else: else:
if job_data["interval_type"] == "hours": if job_data["interval_type"] == "hours":
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
minute=0, minute=0,
hour="*/" + str(job_data["interval"]), hour="*/" + str(job_data["interval"]),
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
elif job_data["interval_type"] == "minutes": elif job_data["interval_type"] == "minutes":
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
minute="*/" + str(job_data["interval"]), minute="*/" + str(job_data["interval"]),
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
elif job_data["interval_type"] == "days": elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":") curr_time = job_data["start_time"].split(":")
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
day="*/" + str(job_data["interval"]), day="*/" + str(job_data["interval"]),
hour=curr_time[0], hour=curr_time[0],
minute=curr_time[1], minute=curr_time[1],
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
logger.info("Added job. Current enabled schedules: ") logger.info("Added job. Current enabled schedules: ")
@ -460,16 +481,19 @@ class TasksManager:
if job_data["cron_string"] != "": if job_data["cron_string"] != "":
try: try:
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
CronTrigger.from_crontab( CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz) job_data["cron_string"], timezone=str(self.tz)
), ),
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
except Exception as e: except Exception as e:
@ -480,45 +504,54 @@ class TasksManager:
else: else:
if job_data["interval_type"] == "hours": if job_data["interval_type"] == "hours":
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
minute=0, minute=0,
hour="*/" + str(job_data["interval"]), hour="*/" + str(job_data["interval"]),
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
elif job_data["interval_type"] == "minutes": elif job_data["interval_type"] == "minutes":
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
minute="*/" + str(job_data["interval"]), minute="*/" + str(job_data["interval"]),
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
elif job_data["interval_type"] == "days": elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":") curr_time = job_data["start_time"].split(":")
new_job = self.scheduler.add_job( new_job = self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"cron", "cron",
day="*/" + str(job_data["interval"]), day="*/" + str(job_data["interval"]),
hour=curr_time[0], hour=curr_time[0],
minute=curr_time[1], minute=curr_time[1],
id=str(sch_id), id=str(sch_id),
args=[ args=[
job_data["server_id"], {
self.users_controller.get_id_by_name("system"), "server_id": job_data["server_id"],
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
job_data["command"], "system"
),
"command": job_data["command"],
}
], ],
) )
if new_job != "error": if new_job != "error":
@ -579,15 +612,18 @@ class TasksManager:
seconds=schedule.delay seconds=schedule.delay
) )
self.scheduler.add_job( self.scheduler.add_job(
HelpersManagement.add_command, self.controller.management.queue_command,
"date", "date",
run_date=delaytime, run_date=delaytime,
id=str(schedule.schedule_id), id=str(schedule.schedule_id),
args=[ args=[
schedule.server_id, {
self.users_controller.get_id_by_name("system"), "server_id": schedule.server_id.server_id,
"127.0.0.1", "user_id": self.users_controller.get_id_by_name(
schedule.command, "system"
),
"command": schedule.command,
}
], ],
) )
else: else:

View File

@ -348,14 +348,11 @@ class AjaxHandler(BaseHandler):
server.backup_server() server.backup_server()
elif page == "clear_comms":
if exec_user["superuser"]:
self.controller.clear_unexecuted_commands()
return
elif page == "select_photo": elif page == "select_photo":
if exec_user["superuser"]: if exec_user["superuser"]:
photo = self.get_argument("photo", None) 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": if photo == "login_1.jpg":
self.controller.management.set_login_image("login_1.jpg") self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = f"{photo}" self.controller.cached_login = f"{photo}"
@ -366,7 +363,7 @@ class AjaxHandler(BaseHandler):
elif page == "delete_photo": elif page == "delete_photo":
if exec_user["superuser"]: if exec_user["superuser"]:
photo = self.get_argument("photo", None) photo = urllib.parse.unquote(self.get_argument("photo", None))
if photo and photo != "login_1.jpg": if photo and photo != "login_1.jpg":
os.remove( os.remove(
os.path.join( os.path.join(
@ -440,15 +437,8 @@ class AjaxHandler(BaseHandler):
for schedule in self.controller.management.get_schedules_by_server( for schedule in self.controller.management.get_schedules_by_server(
server_id server_id
): ):
self.controller.management.create_scheduled_task( self.tasks_manager.update_job(
new_server_id, schedule.schedule_id, {"server_id": new_server_id}
schedule.action,
schedule.interval,
schedule.interval_type,
schedule.start_time,
schedule.command,
schedule.name,
schedule.enabled,
) )
# preserve execution command # preserve execution command
new_server_obj = self.controller.servers.get_server_obj( new_server_obj = self.controller.servers.get_server_obj(
@ -456,6 +446,29 @@ class AjaxHandler(BaseHandler):
) )
new_server_obj.execution_command = server_data["execution_command"] new_server_obj.execution_command = server_data["execution_command"]
self.controller.servers.update_server(new_server_obj) 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 # remove old server's tasks
try: try:
self.tasks_manager.remove_all_server_tasks(server_id) self.tasks_manager.remove_all_server_tasks(server_id)
@ -484,15 +497,8 @@ class AjaxHandler(BaseHandler):
for schedule in self.controller.management.get_schedules_by_server( for schedule in self.controller.management.get_schedules_by_server(
server_id server_id
): ):
self.controller.management.create_scheduled_task( self.tasks_manager.update_job(
new_server_id, schedule.schedule_id, {"server_id": new_server_id}
schedule.action,
schedule.interval,
schedule.interval_type,
schedule.start_time,
schedule.command,
schedule.name,
schedule.enabled,
) )
# preserve execution command # preserve execution command
new_server_obj = self.controller.servers.get_server_obj( new_server_obj = self.controller.servers.get_server_obj(
@ -500,6 +506,29 @@ class AjaxHandler(BaseHandler):
) )
new_server_obj.execution_command = server_data["execution_command"] new_server_obj.execution_command = server_data["execution_command"]
self.controller.servers.update_server(new_server_obj) 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: try:
self.tasks_manager.remove_all_server_tasks(server_id) self.tasks_manager.remove_all_server_tasks(server_id)
except: except:

View File

@ -17,6 +17,5 @@ class DefaultHandler(BaseHandler):
) )
else: else:
self.redirect( self.redirect(
"/public/login", "/panel/dashboard",
# translate=self.translator.translate,
) )

View File

@ -100,7 +100,7 @@ class FileHandler(BaseHandler):
self.write( self.write(
Helpers.get_os_understandable_path(path) Helpers.get_os_understandable_path(path)
+ "\n" + "\n"
+ Helpers.generate_tree(path) + self.helper.generate_tree(path)
) )
self.finish() self.finish()
@ -121,7 +121,7 @@ class FileHandler(BaseHandler):
self.write( self.write(
Helpers.get_os_understandable_path(path) Helpers.get_os_understandable_path(path)
+ "\n" + "\n"
+ Helpers.generate_dir(path) + self.helper.generate_dir(path)
) )
self.finish() self.finish()

View File

@ -291,6 +291,7 @@ class PanelHandler(BaseHandler):
# todo: make this actually pull and compare version data # todo: make this actually pull and compare version data
"update_available": self.helper.update_available, "update_available": self.helper.update_available,
"background": self.controller.cached_login, "background": self.controller.cached_login,
"login_opacity": self.controller.management.get_login_opacity(),
"serverTZ": tz, "serverTZ": tz,
"version_data": self.helper.get_version_string(), "version_data": self.helper.get_version_string(),
"failed_servers": self.controller.servers.failed_servers, "failed_servers": self.controller.servers.failed_servers,
@ -857,13 +858,51 @@ class PanelHandler(BaseHandler):
page_data["roles"] = self.controller.roles.get_all_roles() page_data["roles"] = self.controller.roles.get_all_roles()
page_data["auth-servers"][user.user_id] = super_auth_servers page_data["auth-servers"][user.user_id] = super_auth_servers
page_data["managed_users"] = [] page_data["managed_users"] = []
else:
page_data["managed_users"] = self.controller.users.get_managed_users(
exec_user["user_id"]
)
page_data["assigned_roles"] = []
for item in page_data["roles"]:
page_data["assigned_roles"].append(item.role_id)
page_data["managed_roles"] = self.controller.users.get_managed_roles(
exec_user["user_id"]
)
page_data["active_link"] = "panel_config"
template = "panel/panel_config.html"
elif page == "config_json":
if exec_user["superuser"]:
with open(self.helper.settings_file, "r", encoding="utf-8") as f:
data = json.load(f)
page_data["config-json"] = data
page_data["availables_languages"] = []
page_data["all_languages"] = []
for file in sorted(
os.listdir(
os.path.join(self.helper.root_dir, "app", "translations")
)
):
if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
):
page_data["availables_languages"].append(file.split(".")[0])
page_data["all_languages"].append(file.split(".")[0])
page_data["active_link"] = "config_json"
template = "panel/config_json.html"
elif page == "custom_login":
if exec_user["superuser"]:
page_data["backgrounds"] = [] page_data["backgrounds"] = []
cached_split = self.controller.cached_login.split("/") cached_split = self.controller.cached_login.split("/")
if len(cached_split) == 1: if len(cached_split) == 1:
page_data["backgrounds"].append( page_data["backgrounds"].append(self.controller.cached_login)
self.controller.cached_login
)
else: else:
page_data["backgrounds"].append(cached_split[1]) page_data["backgrounds"].append(cached_split[1])
if "login_1.jpg" not in page_data["backgrounds"]: if "login_1.jpg" not in page_data["backgrounds"]:
@ -883,19 +922,12 @@ class PanelHandler(BaseHandler):
if item not in page_data["backgrounds"]: if item not in page_data["backgrounds"]:
page_data["backgrounds"].append(item) page_data["backgrounds"].append(item)
page_data["background"] = self.controller.cached_login page_data["background"] = self.controller.cached_login
else: page_data[
page_data["managed_users"] = self.controller.users.get_managed_users( "login_opacity"
exec_user["user_id"] ] = self.controller.management.get_login_opacity()
)
page_data["assigned_roles"] = []
for item in page_data["roles"]:
page_data["assigned_roles"].append(item.role_id)
page_data["managed_roles"] = self.controller.users.get_managed_roles( page_data["active_link"] = "custom_login"
exec_user["user_id"] template = "panel/custom_login.html"
)
template = "panel/panel_config.html"
elif page == "add_user": elif page == "add_user":
page_data["new_user"] = True page_data["new_user"] = True
@ -953,7 +985,9 @@ class PanelHandler(BaseHandler):
os.listdir(os.path.join(self.helper.root_dir, "app", "translations")) os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
): ):
if file.endswith(".json"): if file.endswith(".json"):
if file not in self.helper.get_setting("disabled_language_files"): if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
):
if file != str(page_data["languages"][0] + ".json"): if file != str(page_data["languages"][0] + ".json"):
page_data["languages"].append(file.split(".")[0]) page_data["languages"].append(file.split(".")[0])
@ -1164,7 +1198,9 @@ class PanelHandler(BaseHandler):
os.listdir(os.path.join(self.helper.root_dir, "app", "translations")) os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
): ):
if file.endswith(".json"): if file.endswith(".json"):
if file not in self.helper.get_setting("disabled_language_files"): if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
):
if file != str(page_data["languages"][0] + ".json"): if file != str(page_data["languages"][0] + ".json"):
page_data["languages"].append(file.split(".")[0]) page_data["languages"].append(file.split(".")[0])
@ -1678,6 +1714,8 @@ class PanelHandler(BaseHandler):
compress = self.get_argument("compress", False) compress = self.get_argument("compress", False)
shutdown = self.get_argument("shutdown", False) shutdown = self.get_argument("shutdown", False)
check_changed = self.get_argument("changed") check_changed = self.get_argument("changed")
before = self.get_argument("backup_before", "")
after = self.get_argument("backup_after", "")
if str(check_changed) == str(1): if str(check_changed) == str(1):
checked = self.get_body_arguments("root_path") checked = self.get_body_arguments("root_path")
else: else:
@ -1701,6 +1739,8 @@ class PanelHandler(BaseHandler):
excluded_dirs=checked, excluded_dirs=checked,
compress=bool(compress), compress=bool(compress),
shutdown=bool(shutdown), shutdown=bool(shutdown),
before=before,
after=after,
) )
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
@ -1712,6 +1752,41 @@ class PanelHandler(BaseHandler):
self.tasks_manager.reload_schedule_from_db() self.tasks_manager.reload_schedule_from_db()
self.redirect(f"/panel/server_detail?id={server_id}&subpage=backup") 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")
if page == "new_schedule": if page == "new_schedule":
server_id = self.check_server_id() server_id = self.check_server_id()
if not server_id: if not server_id:

View File

@ -40,6 +40,7 @@ class PublicHandler(BaseHandler):
"lang_page": self.helper.get_lang_page(self.helper.get_setting("language")), "lang_page": self.helper.get_lang_page(self.helper.get_setting("language")),
"query": "", "query": "",
"background": self.controller.cached_login, "background": self.controller.cached_login,
"login_opacity": self.controller.management.get_login_opacity(),
} }
if self.request.query: if self.request.query:
@ -61,15 +62,15 @@ class PublicHandler(BaseHandler):
self.clear_cookie("token") self.clear_cookie("token")
# self.clear_cookie("user") # self.clear_cookie("user")
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.redirect("/public/login") self.redirect("/login")
return return
# if we have no page, let's go to login # if we have no page, let's go to login
else: else:
if self.request.query: if self.request.query:
self.redirect("/public/login?" + self.request.query) self.redirect("/login?" + self.request.query)
else: else:
self.redirect("/public/login") self.redirect("/login")
return return
self.render( self.render(
@ -96,9 +97,9 @@ class PublicHandler(BaseHandler):
if page == "login": if page == "login":
next_page = "/public/login" next_page = "/login"
if self.request.query: if self.request.query:
next_page = "/public/login?" + self.request.query next_page = "/login?" + self.request.query
entered_username = bleach.clean(self.get_argument("username")) entered_username = bleach.clean(self.get_argument("username"))
entered_password = bleach.clean(self.get_argument("password")) entered_password = bleach.clean(self.get_argument("password"))
@ -113,11 +114,9 @@ class PublicHandler(BaseHandler):
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token") self.clear_cookie("token")
if self.request.query: if self.request.query:
self.redirect( self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else: else:
self.redirect(f"/public/login?error_msg={error_msg}") self.redirect(f"/login?error_msg={error_msg}")
return return
# if we don't have a user # if we don't have a user
@ -127,11 +126,9 @@ class PublicHandler(BaseHandler):
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token") self.clear_cookie("token")
if self.request.query: if self.request.query:
self.redirect( self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else: else:
self.redirect(f"/public/login?error_msg={error_msg}") self.redirect(f"/login?error_msg={error_msg}")
return return
# if they are disabled # if they are disabled
@ -144,11 +141,9 @@ class PublicHandler(BaseHandler):
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token") self.clear_cookie("token")
if self.request.query: if self.request.query:
self.redirect( self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else: else:
self.redirect(f"/public/login?error_msg={error_msg}") self.redirect(f"/login?error_msg={error_msg}")
return return
login_result = self.helper.verify_pass(entered_password, user_data.password) login_result = self.helper.verify_pass(entered_password, user_data.password)
@ -187,13 +182,11 @@ class PublicHandler(BaseHandler):
user_data.user_id, "Tried to log in", 0, self.get_remote_ip() user_data.user_id, "Tried to log in", 0, self.get_remote_ip()
) )
if self.request.query: if self.request.query:
self.redirect( self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else: else:
self.redirect(f"/public/login?error_msg={error_msg}") self.redirect(f"/login?error_msg={error_msg}")
else: else:
if self.request.query: if self.request.query:
self.redirect("/public/login?" + self.request.query) self.redirect("/login?" + self.request.query)
else: else:
self.redirect("/public/login") self.redirect("/login")

View File

@ -331,7 +331,7 @@ class ServerHandler(BaseHandler):
return return
if import_type == "import_jar": if import_type == "import_jar":
if not self.helper.is_subdir( if self.helper.is_subdir(
import_server_path, self.controller.project_root import_server_path, self.controller.project_root
): ):
self.redirect( self.redirect(

View File

@ -11,6 +11,7 @@ import tornado.escape
import tornado.locale import tornado.locale
import tornado.httpserver import tornado.httpserver
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
@ -110,10 +111,13 @@ class Webserver:
https_port = self.helper.get_setting("https_port") https_port = self.helper.get_setting("https_port")
debug_errors = self.helper.get_setting("show_errors") debug_errors = self.helper.get_setting("show_errors")
cookie_secret = self.helper.get_setting("cookie_secret") try:
cookie_secret = HelpersManagement.get_cookie_secret()
if cookie_secret is False: except:
cookie_secret = False
if cookie_secret is False or cookie_secret == "":
cookie_secret = self.helper.random_string_generator(32) cookie_secret = self.helper.random_string_generator(32)
HelpersManagement.set_cookie_secret(cookie_secret)
if not http_port: if not http_port:
http_port = 8000 http_port = 8000
@ -147,7 +151,6 @@ class Webserver:
} }
handlers = [ handlers = [
(r"/", DefaultHandler, handler_args), (r"/", DefaultHandler, handler_args),
(r"/public/(.*)", PublicHandler, 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"/ajax/(.*)", AjaxHandler, handler_args),
@ -168,6 +171,9 @@ class Webserver:
(r"/api/v1/users/delete_user", DeleteUser, handler_args), (r"/api/v1/users/delete_user", DeleteUser, handler_args),
# API Routes V2 # API Routes V2
*api_handlers(handler_args), *api_handlers(handler_args),
# Using this one at the end
# to catch all the other requests to Public Handler
(r"/(.*)", PublicHandler, handler_args),
] ]
app = tornado.web.Application( app = tornado.web.Application(
@ -179,21 +185,14 @@ class Webserver:
xsrf_cookies=True, xsrf_cookies=True,
autoreload=False, autoreload=False,
log_function=self.log_function, log_function=self.log_function,
login_url="/public/login", login_url="/login",
default_handler_class=PublicHandler, default_handler_class=PublicHandler,
static_handler_class=CustomStaticHandler, static_handler_class=CustomStaticHandler,
serve_traceback=debug_errors, serve_traceback=debug_errors,
) )
http_handers = [ http_handers = [
(r"/", HTTPHandler, handler_args), (r"/", HTTPHandler, handler_args),
(r"/public/(.*)", HTTPHandlerPage, handler_args), (r"/(.+)", HTTPHandlerPage, handler_args),
(r"/panel/(.*)", HTTPHandlerPage, handler_args),
(r"/server/(.*)", HTTPHandlerPage, handler_args),
(r"/ajax/(.*)", HTTPHandlerPage, handler_args),
(r"/api/stats/servers", HTTPHandlerPage, handler_args),
(r"/api/stats/node", HTTPHandlerPage, handler_args),
(r"/ws", HTTPHandlerPage, handler_args),
(r"/upload", HTTPHandlerPage, handler_args),
] ]
http_app = tornado.web.Application( http_app = tornado.web.Application(
http_handers, http_handers,
@ -205,7 +204,7 @@ class Webserver:
autoreload=False, autoreload=False,
log_function=self.log_function, log_function=self.log_function,
default_handler_class=HTTPHandler, default_handler_class=HTTPHandler,
login_url="/public/login", login_url="/login",
serve_traceback=debug_errors, serve_traceback=debug_errors,
) )

View File

@ -1,27 +0,0 @@
{
"http_port": 8000,
"https_port": 8443,
"language": "en_EN",
"cookie_expire": 30,
"cookie_secret": "random",
"apikey_secret": "random",
"show_errors": true,
"history_max_age": 7,
"stats_update_frequency": 30,
"delete_default_json": false,
"show_contribute_link": true,
"virtual_terminal_lines": 70,
"max_log_lines": 700,
"max_audit_entries": 300,
"disabled_language_files": [
"lol_EN.json",
""
],
"stream_size_GB": 1,
"keywords": [
"help",
"chunk"
],
"allow_nsfw_profile_pictures": false,
"enable_user_self_delete": false
}

View File

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

View File

@ -34,16 +34,18 @@
</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"><div class="support_progress" style="height: 15px; width: 100%;"> <span class="dropdown-item" id="support_progress">
<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></span> </div>
</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="/public/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>

View File

@ -0,0 +1,320 @@
{% extends ../base.html %}
{% block meta %}
{% end %}
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
{% block content %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('panelConfig', 'title', data['lang']) }}
<br />
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
{% if data['superuser'] %}
{% include "parts/crafty_config_list.html %}
{% end %}
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<!-- TODO: Translate the following -->
<h4 class="page-title">Config.json</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<form id="config-form" class="forms-sample" method="post" action="/panel/config_json">
{% raw xsrf_form_html() %}
{% for item in data['config-json'].items() %}
{% if item[0] == "reset_secrets_on_next_boot" %}
<div class="form-group" style="background: rgba(243, 21, 6, 0.3); outline: 1px solid red; padding: 10px;">
{% else %}
<div class="form-group">
{% end %}
<label class="form" for="{{item[0]}}">{{item[0]}}
<small class="text-muted ml-1">
</small> </label><br />
{% if item[0] == 'language' %}
<select name="{{item[0]}}" class="form-control">
{% for lang in data['availables_languages'] %}
{% if lang == item[1] %}
<option selected>{{lang}}</option>
{% else %}
<option>{{lang}}</option>
{% end %}
{% end %}
</select>
{% elif item[0] == 'disabled_language_files' %}
<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')
});">Enable all Languages</button>
<select id="lang_select" class="form-control selectpicker show-tick" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for lang in data['all_languages'] %}
{% if lang in item[1] %}
<option selected>{{lang}}</option>
{% else %}
<option>{{lang}}</option>
{% end %}
{% end %}
</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>
</div>
{% elif isinstance(item[1], list) %}
<textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}" class="form-control list">{{','.join(item[1])}}</textarea>
{% elif isinstance(item[1], bool) %}
<div style="margin-left: 30px;">
{% if item[1] == True %}
<input type="radio" class="form-check-input" name="{{item[0]}}" id="True" value="True" checked>
 <label for="True">True</label><br>
<input type="radio" class="form-check-input" name="{{item[0]}}" id="False" value="False">
 <label for="False">False</label>
{% else %}
<input type="radio" class="form-check-input" name="{{item[0]}}" id="True" value="True">
 <label for="True">True</label><br>
<input type="radio" class="form-check-input" name="{{item[0]}}" id="False" value="False" checked>
 <label for="False">False</label>
{% end %}
</div>
{% 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>
{% else %}
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="2" min="0" required>
{% end %}
</div>
{% end %}
<button class="btn btn-success" type="submit">Submit</button>&nbsp;<span id="submit-status"></span>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
.custom-picker {
border: 1px solid var(--outline);
}
.dropdown-menu.inner {
display: inline-block !important;
}
.popover-body {
color: white !important;
;
}
input[type="radio"] {
-ms-transform: scale(1.5);
/* IE 9 */
-webkit-transform: scale(1.5);
/* Chrome, Safari, Opera */
transform: scale(1.5);
}
</style>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
$("#config-form").submit(function (e) {
let uuid = uuidv4();
var token = getCookie("_xsrf")
e.preventDefault();
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
/* Convert multiple select to text list */
let selected_Lang = $('#lang_select').val();
$('#disabled_lang').val(selected_Lang);
let class_list = document.getElementsByClassName("list");
let form_json = convertFormToJSON($("#config-form"));
for (let i = 0; i < class_list.length; i++) {
let str = String($(class_list.item(i)).val())
form_json[$(class_list.item(i)).attr("name")] = uuid + "," + str.replace(/\s/g, '');
};
form_json['uuid'] = uuid;
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
dataType: "text",
url: '/panel/config_json',
data: form_json,
success: function (data) {
$("#submit-status").html('<i class="fa fa-check"></i>');
},
});
});
function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
function convertFormToJSON(form) {
const array = $(form).serializeArray(); // Encodes the set of form elements as an array of names and values.
const json = {};
$.each(array, function () {
json[this.name] = this.value || "";
});
return json;
}
$(document).ready(function () {
$('[data-toggle="popover"]').popover();
if ($(window).width() < 1000) {
$('.too_small').popover("show");
$('.too_small2').popover("show");
}
});
$(window).ready(function () {
$('body').click(function () {
$('.too_small').popover("hide");
$('.too_small2').popover("hide");
});
});
$(window).resize(function () {
// This will execute whenever the window is resized
if ($(window).width() < 1000) {
$('.too_small').popover("show");
}
else {
$('.too_small').popover("hide");
} // New width
if ($(window).width() < 1000) {
$('.too_small2').popover("show");
}
else {
$('.too_small2').popover("hide");
} // New width
});
$('#file').change(function () {
console.log("File changed");
if ($('#file').val()) {
$('#upload-button').prop("disabled", false);
console.log("File changed good");
}
});
</script>
<script>
$(document).ready(function () {
console.log('ready for JS!');
$('.selectpicker').selectpicker("refresh");
});
$(".show_button").click(function () {
console.log("showing key");
api_key = $(this).attr("data-id");
bootbox.alert({
backdrop: true,
title: '',
message: api_key,
});
});
$('.clear-comm').click(function () {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/clear_comm',
success: function (data) {
},
});
})
$('.delete-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_photo?photo=' + photo,
success: function (data) {
location.reload();
},
});
})
$('.select-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/select_photo?photo=' + photo,
success: function (data) {
window.location.reload();
},
});
})
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<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%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>'
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'background'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
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"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
setTimeout(function () {
window.location.reload();
}, 2000);
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/js/bootstrap-select.min.js">
</script>
{% end %}

View File

@ -0,0 +1,391 @@
{% extends ../base.html %}
{% block meta %}
{% end %}
{% block title %}Crafty Controller - {{ translate('customLogin', 'pageTitle', data['lang']) }}{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('panelConfig', 'title', data['lang']) }}
<br />
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
{% if data['superuser'] %}
{% include "parts/crafty_config_list.html %}
{% end %}
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<!-- TODO: Translate the following -->
<h4 class="page-title">{{ translate('customLogin', 'customLoginPage', data['lang']) }}</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-12">
<h4>{{ translate('customLogin', 'loginImage', data['lang']) }}</h4>
<hr>
<form class="form-row" name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
<div class="col form-group">
<span id="upload_input"><input type="file" class="form-control-file" id="file" name="file"
multiple="false" required></span>
</div>
<div class="col form-group">
<button type="button" class="btn btn-info" id="upload-button" onclick="sendFile()"
disabled>UPLOAD</button>
</div>
</form>
<hr>
<hr />
</div>
<div class="col-12">
<div>
<h6>{{ translate('customLogin', 'preview', data['lang']) }}:</h6>
<form id="photo_form">
<div class="form-group row">
<label for="photo" class="col-sm-6 col-form-label">Selected Background Image</label>
<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()">
{% for image in data["backgrounds"] %}
<option value="{{image}}">{{image}}</option>
{% end %}
</select>
</div>
</div>
<div id="photo_loading" class="form-group" hidden>
<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%">&nbsp;<i
class="fa-solid fa-spinner"></i></div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3" for="formControlRange">{{ translate('customLogin', 'loginOpacity',
data['lang']) }}</label>
<label class="col-sm-1" id="opacityValue">{{ data['login_opacity'] }}%</label>
<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'] }}">
</div>
</div>
<div id="login_preview" style="position: relative;">
<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-background" class="auto-form-wrapper login-modal">
<div class="text-center auto-form-logo">
<img src="/static/assets/images/logo_long.svg">
</div>
<style>
#login-form-preview {
display: flex;
position: absolute;
overflow: hidden;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
max-width: 90%;
max-height: 90%;
}
.auto-form-wrapper {
background: rgb(34, 36, 55, 1);
padding: 2rem 2rem 0.5rem;
border-radius: 4px;
-webkit-box-shadow: 0 -25px 37.7px 11.3px rgb(8 143 220 / 7%);
box-shadow: 0 -25px 37.7px 11.3px rgb(8 143 220 / 7%);
color: #fff;
}
/*.auto-form-logo {
background: #222437;
padding: 0rem;
margin: 0.5rem 0rem;
border-radius: 0.2rem;
color: #fff;
}*/
.login-modal {
border-radius: 0.4rem !important;
box-shadow: 0 8px 12px 0 hsla(0, 0%, 0%, 0.2) !important;
}
.login-text-input {
border: none !important;
background-color: hsl(234, 30%, 45%);
color: var(--white) !important;
}
.login-text-input:hover,
.login-text-input:focus {
background-color: hsl(234, 30%, 39%) !important;
}
.login-input {
border-radius: 0.4rem !important;
box-shadow: 0 8px 12px 0 hsla(0, 0%, 0%, 0.2);
transition: all 0.3s ease-in-out;
}
.login-input:hover,
.login-input:focus {
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
}
</style>
<div id="login_form_data">
<input type="hidden" name="_xsrf"
value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
<div class="form-group">
<label class="label">Username</label>
<div class="input-group">
<input type="text" class="form-control login-text-input login-input"
placeholder="Username" name="username" id="username" required="true" disabled>
</div>
</div>
<div class="form-group">
<label class="label">Password</label>
<div class="input-group">
<input type="password" class="form-control login-text-input login-input"
placeholder="Password" name="password" id="password" required="true" disabled>
</div>
</div>
<div class="form-group">
<button class="login-input btn btn-primary submit-btn btn-block" disabled>Log
In</button>
</div>
<fieldset style="color: red; text-align: center;">
<span></span>
</fieldset>
<div class="form-group d-flex justify-content-between">
<div class="form-check form-check-flat mt-0">
&nbsp;
</div>
<a href="#" class="text-small forgot-password" disabled>Forgot Password</a>
</div>
<div class="text-block text-center my-3">
<span class="text-small font-weight-semibold"><a
href="https://craftycontrol.com/">Crafty Control
4.0.20</a> </span>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-outline-success select-photo" type="button">{{
translate('customLogin',
'apply', data['lang']) }}</button>
<button class="btn btn-outline-danger delete-photo" type="button">{{
translate('customLogin',
'delete', data['lang']) }}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.popover-body {
color: white !important;
;
}
</style>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
$(document).ready(function () {
$('[data-toggle="popover"]').popover();
if ($(window).width() < 1000) {
$('.too_small').popover("show");
$('.too_small2').popover("show");
}
});
$(window).ready(function () {
$('body').click(function () {
$('.too_small').popover("hide");
$('.too_small2').popover("hide");
});
});
$(window).resize(function () {
// This will execute whenever the window is resized
if ($(window).width() < 1000) {
$('.too_small').popover("show");
}
else {
$('.too_small').popover("hide");
} // New width
if ($(window).width() < 1000) {
$('.too_small2').popover("show");
}
else {
$('.too_small2').popover("hide");
} // New width
});
$('#file').change(function () {
console.log("File changed");
if ($('#file').val()) {
$('#upload-button').prop("disabled", false);
console.log("File changed good");
}
});
</script>
<script>
$(document).ready(function () {
console.log('ready for JS!')
});
$(".show_button").click(function () {
console.log("showing key");
api_key = $(this).attr("data-id");
bootbox.alert({
backdrop: true,
title: '',
message: api_key,
});
});
$('.delete-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_photo?photo=' + encodeURIComponent(photo),
success: function (data) {
location.reload();
},
});
})
$('.select-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
let opacity = $('#modal_opacity').val();
let enc_photo = encodeURIComponent(photo);
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/select_photo?photo=' + enc_photo + '&opacity=' + opacity,
success: function (data) {
window.location.reload();
},
});
})
$(document).ready(function () {
let opacity = parseInt($("#modal_opacity").val());
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
});
function previewOpacity() {
let opacity = parseInt($("#modal_opacity").val())
console.debug("Selected Opacity = " + opacity + "%");
document.getElementById('opacityValue').innerHTML = (opacity) + "%";
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
}
function updateBackgroundSelect() {
$("#photo").val($("#try_photo").val()).change();
}
function updateBackgroundPreview() {
var img = document.getElementById('bg-preview');
if ($("#photo").val() == "login_1.jpg") {
var src_path = "../../static/assets/images/auth/".concat($("#photo").val());
}
else {
var src_path = "../../static/assets/images/auth/custom/".concat($("#photo").val());
}
img.src = src_path;
}
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<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%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>';
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'background'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
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"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
setTimeout(function () {
window.location.reload();
}, 2000);
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
</script>
{% end %}

View File

@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<!-- Required meta tags --> <!-- Required meta tags -->
<meta charset="utf-8"> <meta charset="utf-8">
@ -20,6 +21,14 @@
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg"> <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" /> <link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head> </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"> <body class="dark-theme">
<div class="container-scroller"> <div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper"> <div class="container-fluid page-body-wrapper full-page-wrapper">
@ -33,11 +42,14 @@
<div class="col-sm-12 grid-margin stretch-card"> <div class="col-sm-12 grid-margin stretch-card">
<div class="card card-statistics social-card google-card card-colored"> <div class="card card-statistics social-card google-card card-colored">
<div class="card-body"> <div class="card-body">
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('accessDenied', 'accessDenied', data['lang']) }}</h4> <h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('accessDenied',
<h5 class="headline font-weight-medium">{{ translate('accessDenied', 'noAccess', data['lang']) }}</h5> 'accessDenied', data['lang']) }}</h4>
<h5 class="headline font-weight-medium">{{ translate('accessDenied', 'noAccess', data['lang']) }}
</h5>
<p class="mb-2 comment font-weight-light"> <p class="mb-2 comment font-weight-light">
{{ translate('accessDenied', 'contactAdmin', data['lang']) }}<br /><br /> {{ 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> <a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{
translate('accessDenied', 'contact', data['lang']) }}</a>
</p> </p>
</div> </div>
</div> </div>
@ -66,4 +78,5 @@
<script src="/static/assets/js/shared/todolist.js"></script> <script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject --> <!-- endinject -->
</body> </body>
</html> </html>

View File

@ -8,6 +8,28 @@
{% block content %} {% block content %}
<div class="content-wrapper"> <div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('panelConfig', 'title', data['lang']) }}
<br />
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
{% if data['superuser'] %}
{% include "parts/crafty_config_list.html %}
{% end %}
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
<div class="row page-title-header"> <div class="row page-title-header">
@ -21,11 +43,6 @@
</div> </div>
<!-- Page Title Header Ends--> <!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
<div class="row"> <div class="row">
<div class="col-md-12 col-lg-12 grid-margin stretch-card"> <div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card"> <div class="card">
@ -33,9 +50,7 @@
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang']) <h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang'])
}}</h4> }}</h4>
{% if data['user_data']['hints'] %} {% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , <span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %} {% end %}
<!-- TODO: Translate the following --> <!-- TODO: Translate the following -->
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{ <div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{
@ -133,9 +148,7 @@
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles', <h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles',
data['lang']) }}</h4> data['lang']) }}</h4>
{% if data['user_data']['hints'] %} {% if data['user_data']['hints'] %}
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , <span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %} {% end %}
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{ <div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('panelConfig', 'newRole', data['lang']) }}</a></div> translate('panelConfig', 'newRole', data['lang']) }}</a></div>
@ -221,74 +234,6 @@
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls', <h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls',
data['lang']) }}</h4> data['lang']) }}</h4>
</div> </div>
<div class="card-body">
<button type="button" class="btn btn-outline-danger clear-comm">{{ translate('panelConfig',
'clearComms', data['lang']) }}</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>{{ translate('panelConfig', 'loginImage', data['lang']) }}</h4>
<br />
<p class="card-description">
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('panelConfig', 'backgroundUpload', data['lang'])
}}</label><br>
<span id="upload_input">
<input type="file" multiple="false" class="form-control" id="file" name="file" required
style="width: 70%;">
<button type="button" class="btn btn-info" id="upload-button" onclick="sendFile()"
disabled>UPLOAD</button>
</span>
</div>
</div>
</div>
</div>
</div>
</form>
</p>
</div>
</div>
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>{{ translate('panelConfig', 'loginBackground', data['lang']) }}</h4><br /><br><br />
<form id="photo_form">
<select class="form-select form-control form-control-lg select-css" id="photo" name="photo"
form="photo_form">
{% for image in data["backgrounds"] %}
<option value="{{image}}">{{image}}</option>
{% end %}
</select>
<div>
<br>
<h6>{{ translate('panelConfig', 'preview', data['lang']) }}:</h6>
<img style="width: 200px; height: 113px;"
src="../../static/assets/images/auth/{{ data['background'] }}">
</div>
<br />
<br />
<button class="btn btn-outline-success select-photo" type="button">{{ translate('panelConfig',
'select', data['lang']) }}</button>
<button class="btn btn-outline-danger delete-photo" type="button">{{ translate('panelConfig',
'delete', data['lang']) }}</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -350,97 +295,5 @@
} }
}); });
</script> </script>
<script>
$(document).ready(function () {
console.log('ready for JS!')
});
$(".show_button").click(function () {
console.log("showing key");
api_key = $(this).attr("data-id");
bootbox.alert({
backdrop: true,
title: '',
message: api_key,
});
});
$('.clear-comm').click(function () {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/clear_comm',
success: function (data) {
},
});
})
$('.delete-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_photo?photo=' + photo,
success: function (data) {
location.reload();
},
});
})
$('.select-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/select_photo?photo=' + photo,
success: function (data) {
window.location.reload();
},
});
})
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<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%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>'
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'background'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
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"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
setTimeout(function () {
window.location.reload();
}, 2000);
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
</script>
{% end %} {% end %}

View File

@ -0,0 +1,14 @@
<ul class="nav nav-tabs col-md-12 tab-simple-styled" role="tablist" style="margin-top: 0;">
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'panel_config' %}active{% end %}" href="/panel/panel_config" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Panel Config</a>
</li>
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'config_json' %}active{% end %}" href="/panel/config_json" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Config.json</a>
</li>
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'custom_login' %}active{% end %}" href="/panel/custom_login" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Custom Login</a>
</li>
</ul>

View File

@ -107,6 +107,40 @@
translate('serverBackups', 'shutdown', data['lang']) }} translate('serverBackups', 'shutdown', data['lang']) }}
{% end %} {% end %}
</div> </div>
<div class="form-group">
<label for="command-check" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['before'] %}
<input type="checkbox" class="form-check-input" id="before-check" name="before-check" checked>Run
Command Before Backup
<br>
<input type="text" class="form-control" name="backup_before" id="backup_before"
value="{{ data['backup_config']['before'] }}" placeholder="We enter the / for you"
style="display: inline-block;">
{% else %}
<input type="checkbox" class="form-check-input" id="before-check" name="before-check">Run Command
Before Backup
<br>
<input type="text" class="form-control" name="backup_before" id="backup_before" value=""
placeholder="We enter the / for you." style="display: none;">
{% end %}
</div>
<div class="form-group">
<label for="command-check" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['after'] %}
<input type="checkbox" class="form-check-input" id="after-check" name="after-check" checked>Run
Command After Backup
<br>
<input type="text" class="form-control" name="backup_after" id="backup_after"
value="{{ data['backup_config']['after'] }}" placeholder="We enter the / for you"
style="display: inline-block;">
{% else %}
<input type="checkbox" class="form-check-input" id="after-check" name="after-check">Run Command
Before Backup
<br>
<input type="text" class="form-control" name="backup_after" id="backup_after" value=""
placeholder="We enter the / for you." style="display: none;">
{% end %}
</div>
<div class="form-group"> <div class="form-group">
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{ <label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{
translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label> translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
@ -344,6 +378,22 @@
}); });
} }
$("#before-check").on("click", function () {
if ($("#before-check:checked").val()) {
$("#backup_before").css("display", "inline-block");
} else {
$("#backup_before").css("display", "none");
$("#backup_before").val("");
}
});
$("#after-check").on("click", function () {
if ($("#after-check:checked").val()) {
$("#backup_after").css("display", "inline-block");
} else {
$("#backup_after").css("display", "none");
$("#backup_after").val("");
}
});
$(document).ready(function () { $(document).ready(function () {
try { try {

View File

@ -697,7 +697,7 @@
}); });
} }
function sendFile(file, path, serverId, left, onProgress) { async function sendFile(file, path, serverId, left, i, onProgress) {
let xmlHttpRequest = new XMLHttpRequest(); let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf") let token = getCookie("_xsrf")
let fileName = file.name let fileName = file.name
@ -719,7 +719,36 @@
onProgress(Math.floor(event.loaded / event.total * 100)), false); onProgress(Math.floor(event.loaded / event.total * 100)), false);
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!');
let caught = false;
try {
var par_el = document.getElementById(path + "ul");
var items = par_el.children;
} catch {
caught = true;
var par_el = document.getElementById("files-tree");
var items = par_el.children;
}
let name = file.name;
console.log(par_el)
let full_path = path + '/' + name
let flag = false;
for (var k = 0; k < items.length; ++k) {
if ($(items[k]).attr("data-name") == name) {
flag = true;
}
}
if (!flag) {
if (caught) {
$(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="d-block tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>');
} else {
$(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>');
}
setTreeViewContext();
}
$(`#upload-progress-bar-${i + 1}`).removeClass("progress-bar-striped");
$(`#upload-progress-bar-${i + 1}`).addClass("bg-success");
$(`#upload-progress-bar-${i + 1}`).html('<i style="color: black;" class="fas fa-box-check"></i>')
} }
else { else {
alert('Upload failed with response: ' + event.target.responseText); alert('Upload failed with response: ' + event.target.responseText);
@ -735,7 +764,7 @@
let uploadWaitDialog; let uploadWaitDialog;
let doUpload = true; let doUpload = true;
function uploadFilesE(event) { async function uploadFilesE(event) {
path = event.target.parentElement.getAttribute('data-path'); path = event.target.parentElement.getAttribute('data-path');
$(function () { $(function () {
var uploadHtml = "<div>" + var uploadHtml = "<div>" +
@ -795,7 +824,7 @@
`; `;
$('#upload-progress-bar-parent').append(progressHtml); $('#upload-progress-bar-parent').append(progressHtml);
sendFile(files.files[i], path, serverId, nFiles - i - 1, (progress) => { await sendFile(files.files[i], path, serverId, nFiles - i - 1, i, (progress) => {
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress) $(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%') $(`#upload-progress-bar-${i + 1}`).css('width', progress + '%')
}); });
@ -996,7 +1025,6 @@
function hideUploadBox() { function hideUploadBox() {
if (!uploadWaitDialog) return; if (!uploadWaitDialog) return;
uploadWaitDialog.modal('hide'); uploadWaitDialog.modal('hide');
getTreeView();
} }
function createFileE(event) { function createFileE(event) {
@ -1074,7 +1102,8 @@
callback: function (result) { callback: function (result) {
if (!result) return; if (!result) return;
deleteFile(path, function () { deleteFile(path, function () {
getTreeView() el = document.getElementById(path + "li");
$(el).remove();
document.getElementById('files-tree-nav').style.display = 'none'; document.getElementById('files-tree-nav').style.display = 'none';
}); });
} }
@ -1102,7 +1131,8 @@
callback: function (result) { callback: function (result) {
if (!result) return; if (!result) return;
deleteDir(path, function () { deleteDir(path, function () {
getTreeView() el = document.getElementById(path + "li");
$(el).remove();
document.getElementById('files-tree-nav').style.display = 'none'; document.getElementById('files-tree-nav').style.display = 'none';
}); });
} }

View File

@ -23,7 +23,7 @@
</head> </head>
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("../../static/assets/images/auth/login-1.jpg");
background-size: cover; background-size: cover;
} }

View File

@ -23,7 +23,7 @@
</head> </head>
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("../../static/assets/images/auth/login-1.jpg");
background-size: cover; background-size: cover;
} }

View File

@ -23,9 +23,10 @@
</head> </head>
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("../../static/assets/images/auth/login-1.jpg");
background-size: cover; background-size: cover;
background-position: center;
} }
</style> </style>
@ -36,8 +37,9 @@
<div class="row w-100"> <div class="row w-100">
<div class="col-lg-4 mx-auto"> <div class="col-lg-4 mx-auto">
<div class="auto-form-wrapper login-modal"> <div id="login-form-background" class="auto-form-wrapper login-modal">
<div class="text-center"> <div id="login_opacity" data-value="{{ data['login_opacity'] }}" hidden></div>
<div class="text-center auto-form-logo">
<img src="/static/assets/images/logo_long.svg"> <img src="/static/assets/images/logo_long.svg">
</div> </div>
<style> <style>
@ -69,9 +71,9 @@
} }
</style> </style>
{% if data['query'] %} {% if data['query'] %}
<form action="/public/login?{{ data['query'] }}" method="post"> <form action="/login?{{ data['query'] }}" method="post">
{% else %} {% else %}
<form action="/public/login" method="post"> <form action="/login" method="post">
{% end %} {% end %}
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<div class="form-group"> <div class="form-group">
@ -133,6 +135,13 @@
<script src="/static/assets/js/shared/settings.js"></script> <script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script> <script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject --> <!-- endinject -->
<script>
$(document).ready(function () {
let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
});
</script>
</body> </body>
</html> </html>

View File

@ -9,7 +9,7 @@
<!-- View for Large screen --> <!-- View for Large screen -->
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("../../static/assets/images/auth/login-1.jpg");
background-size: cover; background-size: cover;
} }

View File

@ -9,7 +9,7 @@
<!-- <img src="/static/assets/images/logo_long.svg">--> <!-- <img src="/static/assets/images/logo_long.svg">-->
{{ _('Configure Your Existing Server') }}<br /><br /> {{ _('Configure Your Existing Server') }}<br /><br />
</div> </div>
<form action="/public/login" method="post"> <form action="/login" method="post">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<div class="form-group"> <div class="form-group">

View File

@ -0,0 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns(
"crafty_settings", login_opacity=peewee.IntegerField(default=100)
)
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("crafty_settings", ["login_opacity"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,35 @@
# Generated by database migrator
import datetime
from peewee import *
from app.classes.models.users import Users
from app.classes.models.servers import Servers
def migrate(migrator, database, **kwargs):
migrator.drop_table("commands")
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
db = database
class Commands(Model):
command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref="server", index=True)
user = ForeignKeyField(Users, backref="user", index=True)
source_ip = CharField(default="127.0.0.1")
command = CharField(default="")
executed = BooleanField(default=False)
class Meta:
table_name = "commands"
database = db
migrator.create_table(Commands)
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("backups", before=peewee.CharField(default=""))
migrator.add_columns("backups", after=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("backups", ["before"])
migrator.drop_columns("backups", ["after"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("crafty_settings", cookie_secret=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("crafty_settings", ["cookie_secret"])
"""
Write your rollback migrations here.
"""

View File

@ -213,6 +213,8 @@
"assignedRoles": "Assigned Roles", "assignedRoles": "Assigned Roles",
"cancel": "Cancel", "cancel": "Cancel",
"clearComms": "Clear Un-executed Commands", "clearComms": "Clear Un-executed Commands",
"select": "Select",
"apply": "Apply",
"delete": "Delete", "delete": "Delete",
"edit": "Edit", "edit": "Edit",
"enabled": "Enabled", "enabled": "Enabled",
@ -228,12 +230,20 @@
"superConfirmTitle": "Enable superuser? Are you sure?", "superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User", "user": "User",
"users": "Users", "users": "Users",
"title": "Crafty Configuration"
},
"customLogin": {
"customLoginPage": "Customise the Login Page",
"loginImage": "Upload a background image for the login screen.", "loginImage": "Upload a background image for the login screen.",
"backgroundUpload": "Background Upload", "backgroundUpload": "Background Upload",
"loginBackground": "Login Background Image", "loginBackground": "Login Background Image",
"loginOpacity": "Select the Login Window Opacity",
"select": "Select", "select": "Select",
"apply": "Apply",
"delete": "Delete",
"selectImage": "Select an image", "selectImage": "Select an image",
"preview": "Preview" "preview": "Preview",
"pageTitle": "Custom Login Page"
}, },
"rolesConfig": { "rolesConfig": {
"config": "Role Config", "config": "Role Config",

View File

@ -18,16 +18,6 @@ if [ ! "$(ls -A --ignore=.gitkeep ./app/config)" ]; then
else else
# Keep version file up to date with image # Keep version file up to date with image
cp -f ./app/config_original/version.json ./app/config/version.json cp -f ./app/config_original/version.json ./app/config/version.json
# Compare if user's config is different from image, and show differences.
echo "\033[36mWrapper | \033[35m🏗 Checking for config.json changes..."
cp -f ./app/config_original/config.json ./app/config/config_image_template
if [ "$(diff -q ./app/config/config.json ./app/config/config_image_template)" ]; then
echo "\033[36mWrapper | \033[33m👷 We've found differences in your local config, please review!,"
echo "\033[36m | \033[33m (This could be an outdated config.json)"
else
echo "\033[36mWrapper | \033[32m✅ Config good! Proceeding..."
fi
fi fi

20
main.py
View File

@ -14,6 +14,7 @@ from app.classes.shared.import3 import Import3
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.models.users import HelperUsers from app.classes.models.users import HelperUsers
from app.classes.models.management import HelpersManagement
from app.classes.shared.import_helper import ImportHelpers from app.classes.shared.import_helper import ImportHelpers
console = Console() console = Console()
@ -53,6 +54,9 @@ def do_intro():
""" """
Console.magenta(intro) Console.magenta(intro)
if not helper.check_file_exists(helper.settings_file):
Console.debug("No settings file detected. Creating one.")
helper.set_settings(Helpers.get_master_config())
def setup_logging(debug=True): def setup_logging(debug=True):
@ -121,7 +125,8 @@ if __name__ == "__main__":
# do our installer stuff # do our installer stuff
user_helper = HelperUsers(database, helper) user_helper = HelperUsers(database, helper)
installer = DatabaseBuilder(database, helper, user_helper) management_helper = HelpersManagement(database, helper)
installer = DatabaseBuilder(database, helper, user_helper, management_helper)
FRESH_INSTALL = installer.is_fresh_install() FRESH_INSTALL = installer.is_fresh_install()
if FRESH_INSTALL: if FRESH_INSTALL:
@ -135,10 +140,22 @@ if __name__ == "__main__":
installer.default_settings() installer.default_settings()
else: else:
Console.debug("Existing install detected") Console.debug("Existing install detected")
Console.info("Checking for reset secret flag")
if helper.get_setting("reset_secrets_on_next_boot"):
Console.info("Found Reset")
management_helper.set_secret_api_key(str(helper.random_string_generator(64)))
management_helper.set_cookie_secret(str(helper.random_string_generator(32)))
helper.set_setting("reset_secrets_on_next_boot", False)
else:
Console.info("No flag found. Secrets are staying")
file_helper = FileHelpers(helper) file_helper = FileHelpers(helper)
import_helper = ImportHelpers(helper, file_helper) import_helper = ImportHelpers(helper, file_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, file_helper, import_helper) controller = Controller(database, helper, file_helper, import_helper)
Console.info("Checking for remote changes to config.json")
controller.get_config_diff()
Console.info("Remote change complete.")
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()
@ -222,7 +239,6 @@ if __name__ == "__main__":
Console.debug(f"Execution Mode: {running_mode}") Console.debug(f"Execution Mode: {running_mode}")
Console.debug(f"Application path : '{application_path}'") Console.debug(f"Application path : '{application_path}'")
controller.clear_unexecuted_commands()
controller.clear_support_status() controller.clear_support_status()
crafty_prompt = MainPrompt( crafty_prompt = MainPrompt(