diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc64adc8..ba4e47a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,9 +3,16 @@
### New features
TBD
### Bug fixes
-TBD
+- Fix bug where trying to reconfigure unloaded server would stack ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/1b2fef06fb3b02b76c9506caf7e07e932df95fab) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/460))
+- Fix traceback error when a user click the roles config tab while already on the roles config page; **this is for new role creation only** ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/452))
+- Fix logic issue when removing items from backup exclusions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/453))
+- Cleanup various JS errors ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/455))
+- Temp fix for `&` issue in pathing and minecraft colour codes ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/457))
+- Cache Gravatar pfp's as to not query every page load ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/459))
+- Fix crash on client list changing while sending websockets ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/461))
### Tweaks
-TBD
+- Add button to scroll to bottom of vterm ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/454))
+- Persist schedules and execution commands across backup restores ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/458))
### Lang
TBD
diff --git a/app/classes/controllers/users_controller.py b/app/classes/controllers/users_controller.py
index 13b8fb4f..d7ce4393 100644
--- a/app/classes/controllers/users_controller.py
+++ b/app/classes/controllers/users_controller.py
@@ -147,14 +147,24 @@ class UsersController:
return HelperServers.get_total_owned_servers(exec_user_id)
def update_user(self, user_id: str, user_data=None, user_crafty_data=None):
+ # check if user crafty perms were updated
if user_crafty_data is None:
user_crafty_data = {}
+ # check if general user data was updated
if user_data is None:
user_data = {}
+ # get current user data
base_data = HelperUsers.get_user(user_id)
up_data = {}
+ # check if we updated user email. If so we update gravatar
+ if user_data["email"]:
+ pfp = self.helper.get_gravatar_image(user_data["email"])
+ up_data["pfp"] = pfp
+ # create sets to store role data
added_roles = set()
removed_roles = set()
+
+ # search for changes in user data
for key in user_data:
if key == "user_id":
continue
@@ -174,8 +184,10 @@ class UsersController:
up_data["hints"] = user_data["hints"]
elif base_data[key] != user_data[key]:
up_data[key] = user_data[key]
+ # change last update for user
up_data["last_update"] = self.helper.get_time_as_string()
logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}")
+
for role in added_roles:
HelperUsers.get_or_create(user_id=user_id, role_id=role)
permissions_mask = user_crafty_data.get("permissions_mask", "000")
diff --git a/app/classes/models/users.py b/app/classes/models/users.py
index 0d8e596b..a6e87316 100644
--- a/app/classes/models/users.py
+++ b/app/classes/models/users.py
@@ -42,6 +42,7 @@ class Users(BaseModel):
preparing = BooleanField(default=False)
hints = BooleanField(default=True)
manager = IntegerField(default=None, null=True)
+ pfp = CharField(default="/static/assets/images/faces-clipart/pic-3.png")
class Meta:
table_name = "users"
@@ -220,6 +221,7 @@ class HelperUsers:
Users.password: pw_enc,
Users.email: email,
Users.enabled: enabled,
+ Users.pfp: self.helper.get_gravatar_image(email),
Users.superuser: superuser,
Users.created: Helpers.get_time_as_string(),
Users.manager: manager,
diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py
index 28edbef7..82b5a560 100644
--- a/app/classes/shared/file_helpers.py
+++ b/app/classes/shared/file_helpers.py
@@ -226,18 +226,24 @@ class FileHelpers:
comment, "utf-8"
) # comments over 65535 bytes will be truncated
for root, dirs, files in os.walk(path_to_zip, topdown=True):
- for l_dir in dirs:
+ for l_dir in dirs[:]:
+ # make all paths in exclusions a unix style slash
+ # to match directories.
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
dirs.remove(l_dir)
ziproot = path_to_zip
+ # iterate through list of files
for file in files:
+ # check if file/dir is in exclusions list.
+ # Only proceed if not exluded.
if (
str(os.path.join(root, file)).replace("\\", "/")
not in ex_replace
and file != "crafty.sqlite"
):
try:
- logger.info(f"backing up: {os.path.join(root, file)}")
+ logger.debug(f"backing up: {os.path.join(root, file)}")
+ # add trailing slash to zip root dir if not windows.
if os.name == "nt":
zip_file.write(
os.path.join(root, file),
@@ -254,12 +260,20 @@ class FileHelpers:
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
+ # debug logging for exlusions list
+ else:
+ logger.debug(f"Found {file} in exclusion list. Skipping...")
+
+ # add current file bytes to total bytes.
total_bytes += os.path.getsize(os.path.join(root, file))
+ # calcualte percentage based off total size and current archive size
percent = round((total_bytes / dir_bytes) * 100, 2)
+ # package results
results = {
"percent": percent,
"total_files": self.helper.human_readable_file_size(dir_bytes),
}
+ # send status results to page.
self.helper.websocket_helper.broadcast_page_params(
"/panel/server_detail",
{"id": str(server_id)},
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index be278578..aa53132e 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -20,6 +20,7 @@ import itertools
from datetime import datetime
from socket import gethostname
from contextlib import redirect_stderr, suppress
+import libgravatar
from packaging import version as pkg_version
from app.classes.shared.null_writer import NullWriter
@@ -658,6 +659,33 @@ class Helpers:
return True
return False
+ def get_gravatar_image(self, email):
+ profile_url = "/static/assets/images/faces-clipart/pic-3.png"
+ # http://en.gravatar.com/site/implement/images/#rating
+ if self.get_setting("allow_nsfw_profile_pictures"):
+ rating = "x"
+ else:
+ rating = "g"
+
+ # Get grvatar hash for profile pictures
+ if not self.check_internet() or email != "default@example.com" or email != "":
+ gravatar = libgravatar.Gravatar(libgravatar.sanitize_email(email))
+ url = gravatar.get_image(
+ size=80,
+ default="404",
+ force_default=False,
+ rating=rating,
+ filetype_extension=False,
+ use_ssl=True,
+ ) # + "?d=404"
+ try:
+ if requests.head(url).status_code != 404:
+ profile_url = url
+ except Exception as e:
+ logger.debug(f"Could not pull resource from Gravatar with error {e}")
+
+ return profile_url
+
@staticmethod
def get_file_contents(path: str, lines=100):
diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py
index a654df6d..f3b5b24b 100644
--- a/app/classes/shared/tasks.py
+++ b/app/classes/shared/tasks.py
@@ -666,6 +666,12 @@ class TasksManager:
logger.info(
"No updates found! You are on the most up to date Crafty version."
)
+ logger.info("Refreshing Gravatar PFPs...")
+ for user in HelperUsers.get_all_users():
+ if user.email:
+ HelperUsers.update_user(
+ user.id, {"pfp": self.helper.get_gravatar_image(user.email)}
+ )
def log_watcher(self):
self.controller.servers.check_for_old_logs()
diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py
index bd8f9424..845b2886 100644
--- a/app/classes/web/ajax_handler.py
+++ b/app/classes/web/ajax_handler.py
@@ -383,6 +383,8 @@ class AjaxHandler(BaseHandler):
zip_name = bleach.clean(self.get_argument("zip_file", None))
svr_obj = self.controller.servers.get_server_obj(server_id)
server_data = self.controller.servers.get_server_data_by_id(server_id)
+
+ # import the server again based on zipfile
if server_data["type"] == "minecraft-java":
backup_path = svr_obj.backup_path
if Helpers.validate_traversal(backup_path, zip_name):
@@ -401,6 +403,27 @@ class AjaxHandler(BaseHandler):
self.controller.rename_backup_dir(
server_id, new_server_id, new_server["server_uuid"]
)
+ # preserve current schedules
+ for schedule in self.controller.management.get_schedules_by_server(
+ server_id
+ ):
+ self.controller.management.create_scheduled_task(
+ new_server_id,
+ schedule.action,
+ schedule.interval,
+ schedule.interval_type,
+ schedule.start_time,
+ schedule.command,
+ schedule.name,
+ schedule.enabled,
+ )
+ # preserve execution command
+ new_server_obj = self.controller.servers.get_server_obj(
+ new_server_id
+ )
+ new_server_obj.execution_command = server_data["execution_command"]
+ self.controller.servers.update_server(new_server_obj)
+ # remove old server's tasks
try:
self.tasks_manager.remove_all_server_tasks(server_id)
except:
@@ -424,6 +447,26 @@ class AjaxHandler(BaseHandler):
self.controller.rename_backup_dir(
server_id, new_server_id, new_server["server_uuid"]
)
+ # preserve current schedules
+ for schedule in self.controller.management.get_schedules_by_server(
+ server_id
+ ):
+ self.controller.management.create_scheduled_task(
+ new_server_id,
+ schedule.action,
+ schedule.interval,
+ schedule.interval_type,
+ schedule.start_time,
+ schedule.command,
+ schedule.name,
+ schedule.enabled,
+ )
+ # preserve execution command
+ new_server_obj = self.controller.servers.get_server_obj(
+ new_server_id
+ )
+ new_server_obj.execution_command = server_data["execution_command"]
+ self.controller.servers.update_server(new_server_obj)
try:
self.tasks_manager.remove_all_server_tasks(server_id)
except:
diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py
index b9a69c48..e772d633 100644
--- a/app/classes/web/base_handler.py
+++ b/app/classes/web/base_handler.py
@@ -104,7 +104,10 @@ class BaseHandler(tornado.web.RequestHandler):
strip: bool = True,
) -> t.Optional[str]:
arg = self._get_argument(name, default, self.request.arguments, strip)
- return self.autobleach(name, arg)
+ bleached = self.autobleach(name, arg)
+ if "&" in str(bleached):
+ bleached = bleached.replace("&", "&")
+ return bleached
def get_arguments(self, name: str, strip: bool = True) -> t.List[str]:
if not isinstance(strip, bool):
diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py
index 266d6d3c..faf2df04 100644
--- a/app/classes/web/panel_handler.py
+++ b/app/classes/web/panel_handler.py
@@ -8,7 +8,6 @@ import logging
import threading
import shlex
import bleach
-import libgravatar
import requests
import tornado.web
import tornado.escape
@@ -331,37 +330,6 @@ class PanelHandler(BaseHandler):
"superuser": superuser,
}
- # http://en.gravatar.com/site/implement/images/#rating
- if self.helper.get_setting("allow_nsfw_profile_pictures"):
- rating = "x"
- else:
- rating = "g"
-
- # Get grvatar hash for profile pictures
- if exec_user["email"] != "default@example.com" or "":
- gravatar = libgravatar.Gravatar(
- libgravatar.sanitize_email(exec_user["email"])
- )
- url = gravatar.get_image(
- size=80,
- default="404",
- force_default=False,
- rating=rating,
- filetype_extension=False,
- use_ssl=True,
- ) # + "?d=404"
- try:
- if requests.head(url).status_code != 404:
- profile_url = url
- else:
- profile_url = "/static/assets/images/faces-clipart/pic-3.png"
- except:
- profile_url = "/static/assets/images/faces-clipart/pic-3.png"
- else:
- profile_url = "/static/assets/images/faces-clipart/pic-3.png"
-
- page_data["user_image"] = profile_url
-
if page == "unauthorized":
template = "panel/denied.html"
@@ -549,7 +517,7 @@ class PanelHandler(BaseHandler):
"log_path": server_temp_obj["log_path"],
"executable": server_temp_obj["executable"],
"execution_command": server_temp_obj["execution_command"],
- "shutdown_timeout": server_obj["shutdown_timeout"],
+ "shutdown_timeout": server_temp_obj["shutdown_timeout"],
"stop_command": server_temp_obj["stop_command"],
"executable_update_url": server_temp_obj[
"executable_update_url"
@@ -1732,7 +1700,7 @@ class PanelHandler(BaseHandler):
if interval_type == "days":
sch_time = bleach.clean(self.get_argument("time", None))
if action == "command":
- command = bleach.clean(self.get_argument("command", None))
+ command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
@@ -1748,7 +1716,7 @@ class PanelHandler(BaseHandler):
delay = bleach.clean(self.get_argument("delay", None))
parent = bleach.clean(self.get_argument("parent", None))
if action == "command":
- command = bleach.clean(self.get_argument("command", None))
+ command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
@@ -1768,7 +1736,7 @@ class PanelHandler(BaseHandler):
return
action = bleach.clean(self.get_argument("action", None))
if action == "command":
- command = bleach.clean(self.get_argument("command", None))
+ command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
@@ -1894,7 +1862,7 @@ class PanelHandler(BaseHandler):
if interval_type == "days":
sch_time = bleach.clean(self.get_argument("time", None))
if action == "command":
- command = bleach.clean(self.get_argument("command", None))
+ command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
@@ -1909,7 +1877,7 @@ class PanelHandler(BaseHandler):
delay = bleach.clean(self.get_argument("delay", None))
parent = bleach.clean(self.get_argument("parent", None))
if action == "command":
- command = bleach.clean(self.get_argument("command", None))
+ command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
@@ -1929,7 +1897,7 @@ class PanelHandler(BaseHandler):
return
action = bleach.clean(self.get_argument("action", None))
if action == "command":
- command = bleach.clean(self.get_argument("command", None))
+ command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
diff --git a/app/classes/web/routes/api/users/user/pfp.py b/app/classes/web/routes/api/users/user/pfp.py
index a4f0a480..2bc10ba5 100644
--- a/app/classes/web/routes/api/users/user/pfp.py
+++ b/app/classes/web/routes/api/users/user/pfp.py
@@ -1,6 +1,4 @@
import logging
-import libgravatar
-import requests
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
@@ -21,29 +19,5 @@ class ApiUsersUserPfpHandler(BaseApiHandler):
f'User {auth_data[4]["user_id"]} is fetching the pfp for user {user_id}'
)
- # http://en.gravatar.com/site/implement/images/#rating
- if self.helper.get_setting("allow_nsfw_profile_pictures"):
- rating = "x"
- else:
- rating = "g"
-
- # Get grvatar hash for profile pictures
- if user["email"] != "default@example.com" or "":
- gravatar = libgravatar.Gravatar(libgravatar.sanitize_email(user["email"]))
- url = gravatar.get_image(
- size=80,
- default="404",
- force_default=False,
- rating=rating,
- filetype_extension=False,
- use_ssl=True,
- )
- try:
- requests.head(url).raise_for_status()
- except requests.HTTPError as e:
- logger.debug("Gravatar profile picture not found", exc_info=e)
- else:
- self.finish_json(200, {"status": "ok", "data": url})
- return
-
- self.finish_json(200, {"status": "ok", "data": None})
+ self.finish_json(200, {"status": "ok", "data": user["pfp"]})
+ return
diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py
index 1c1d6fc6..b2419905 100644
--- a/app/classes/web/server_handler.py
+++ b/app/classes/web/server_handler.py
@@ -5,8 +5,6 @@ import time
import tornado.web
import tornado.escape
import bleach
-import libgravatar
-import requests
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.shared.helpers import Helpers
@@ -133,34 +131,6 @@ class ServerHandler(BaseHandler):
"superuser": superuser,
}
- if self.helper.get_setting("allow_nsfw_profile_pictures"):
- rating = "x"
- else:
- rating = "g"
-
- if exec_user["email"] != "default@example.com" or "":
- gravatar = libgravatar.Gravatar(
- libgravatar.sanitize_email(exec_user["email"])
- )
- url = gravatar.get_image(
- size=80,
- default="404",
- force_default=False,
- rating=rating,
- filetype_extension=False,
- use_ssl=True,
- ) # + "?d=404"
- try:
- if requests.head(url).status_code != 404:
- profile_url = url
- else:
- profile_url = "/static/assets/images/faces-clipart/pic-3.png"
- except:
- profile_url = "/static/assets/images/faces-clipart/pic-3.png"
- else:
- profile_url = "/static/assets/images/faces-clipart/pic-3.png"
-
- page_data["user_image"] = profile_url
if superuser:
page_data["roles"] = list_roles
diff --git a/app/classes/web/websocket_helper.py b/app/classes/web/websocket_helper.py
index 4946242b..0346d672 100644
--- a/app/classes/web/websocket_helper.py
+++ b/app/classes/web/websocket_helper.py
@@ -27,7 +27,7 @@ class WebSocketHelper:
f"Sending to {len(self.clients)} clients: "
f"{json.dumps({'event': event_type, 'data': data})}"
)
- for client in self.clients:
+ for client in self.clients[:]:
try:
self.send_message(client, event_type, data)
except Exception as e:
@@ -91,7 +91,7 @@ class WebSocketHelper:
f"clients: {json.dumps({'event': event_type, 'data': data})}"
)
- for client in clients:
+ for client in clients[:]:
try:
self.send_message(client, event_type, data)
except Exception as e:
diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html
index 100ab16c..e2634486 100755
--- a/app/frontend/templates/base.html
+++ b/app/frontend/templates/base.html
@@ -525,10 +525,6 @@
});
});
-
- $(window).unload(function () {
- jQuery.get("/public/logout")
- });
{% block js %}
diff --git a/app/frontend/templates/notify.html b/app/frontend/templates/notify.html
index 07659b12..78f62409 100644
--- a/app/frontend/templates/notify.html
+++ b/app/frontend/templates/notify.html
@@ -18,10 +18,10 @@