Merge branch 'dev' into feature/steamcmd

This commit is contained in:
Zedifus 2022-12-01 00:17:55 +00:00
commit 7b07ac088b
24 changed files with 497 additions and 138 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ venv.bak/
.idea/ .idea/
/imports/ /imports/
/servers/ /servers/
/app/frontend/static/assets/images/auth/custom/
/backups/ /backups/
/temp/ /temp/
/docker/servers/ /docker/servers/

View File

@ -1,13 +1,14 @@
# Changelog # Changelog
## --- [4.0.17] - 2022/TBD ## --- [4.0.17] - 2022/11/30
### New features ### New features
TBD - Automate forge install process through Crafty server creation for Forge server version 1.16 and greater. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/495))
- Tooltip for server port on dashboard. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/496))
- Custom login image backgrounds. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/494))
### Bug fixes ### Bug fixes
TBD - Fix no port on bedrock server creation. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/493))
### Tweaks ### Tweaks
TBD - Docker🐋 | Update image base to Ubuntu 22.04 Jammy ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/497))<br>
### Lang *(OpenJDK16 Removed, no jammy backport)*
TBD
<br><br> <br><br>
## --- [4.0.16] - 2022/10/23 ## --- [4.0.16] - 2022/10/23

View File

@ -1,4 +1,4 @@
FROM ubuntu:20.04 FROM ubuntu:22.04
ENV DEBIAN_FRONTEND="noninteractive" ENV DEBIAN_FRONTEND="noninteractive"
@ -24,7 +24,6 @@ RUN apt-get update \
default-jre \ default-jre \
openjdk-8-jre-headless \ openjdk-8-jre-headless \
openjdk-11-jre-headless \ openjdk-11-jre-headless \
openjdk-16-jre-headless \
openjdk-17-jre-headless \ openjdk-17-jre-headless \
&& apt-get autoremove \ && apt-get autoremove \
&& apt-get clean && apt-get clean

View File

@ -94,6 +94,14 @@ class ManagementController:
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)

View File

@ -119,9 +119,15 @@ class ServersController(metaclass=Singleton):
return srv.stats_helper.set_import() return srv.stats_helper.set_import()
@staticmethod @staticmethod
def finish_import(server_id): def finish_import(server_id, forge=False):
srv = ServersController().get_server_instance_by_id(server_id) srv = ServersController().get_server_instance_by_id(server_id)
return srv.stats_helper.finish_import() # This is where we start the forge installerr
if forge:
srv.run_threaded_server(
HelperUsers.get_user_id_by_name("system"), forge_install=True
)
else:
srv.stats_helper.finish_import()
@staticmethod @staticmethod
def get_import_status(server_id): def get_import_status(server_id):

View File

@ -187,24 +187,22 @@ class ServerJars:
# open a file stream # open a file stream
with requests.get(fetch_url, timeout=2, stream=True) as r: with requests.get(fetch_url, timeout=2, stream=True) as r:
success = False
try: try:
with open(path, "wb") as output: with open(path, "wb") as output:
shutil.copyfileobj(r.raw, output) shutil.copyfileobj(r.raw, output)
# If this is the newer forge version we will run the installer
if server == "forge" and int(version.split(".")[1]) > 15:
ServersController.finish_import(server_id, True)
else:
ServersController.finish_import(server_id) ServersController.finish_import(server_id)
for user in server_users: success = True
self.helper.websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3)
self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {}
)
return True
except Exception as e: except Exception as e:
logger.error(f"Unable to save jar to {path} due to error:{e}") logger.error(f"Unable to save jar to {path} due to error:{e}")
ServersController.finish_import(server_id) ServersController.finish_import(server_id)
server_users = PermissionsServers.get_server_user_list(server_id) server_users = PermissionsServers.get_server_user_list(server_id)
for user in server_users: for user in server_users:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
user, "notification", "Executable download finished" user, "notification", "Executable download finished"
@ -213,5 +211,4 @@ class ServerJars:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {} user, "send_start_reload", {}
) )
return success
return False

View File

@ -43,6 +43,7 @@ class AuditLog(BaseModel):
# ********************************************************************************** # **********************************************************************************
class CraftySettings(BaseModel): class CraftySettings(BaseModel):
secret_api_key = CharField(default="") secret_api_key = CharField(default="")
login_photo = CharField(default="login_1.jpg")
class Meta: class Meta:
table_name = "crafty_settings" table_name = "crafty_settings"
@ -254,6 +255,19 @@ class HelpersManagement:
) )
return settings[0].secret_api_key return settings[0].secret_api_key
@staticmethod
def get_login_image():
settings = CraftySettings.select(CraftySettings.login_photo).where(
CraftySettings.id == 1
)
return settings[0].login_photo
@staticmethod
def set_login_image(photo):
CraftySettings.update({CraftySettings.login_photo: photo}).where(
CraftySettings.id == 1
).execute()
# ********************************************************************************** # **********************************************************************************
# Schedules Methods # Schedules Methods
# ********************************************************************************** # **********************************************************************************

View File

@ -75,6 +75,7 @@ class Controller:
timezone=str(tz) timezone=str(tz)
) )
self.first_login = False self.first_login = False
self.cached_login = self.management.get_login_image()
self.support_scheduler.start() self.support_scheduler.start()
@staticmethod @staticmethod
@ -486,11 +487,26 @@ class Controller:
return False return False
if Helpers.is_os_windows(): if Helpers.is_os_windows():
# Let's check for and setup for install server commands
if server == "forge":
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f'-jar "{server_file}" --installServer'
)
else:
server_command = ( server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M " f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M " f"-Xmx{Helpers.float_to_string(max_mem)}M "
f'-jar "{server_file}" nogui' f'-jar "{server_file}" nogui'
) )
else:
if server == "forge":
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f"-jar {server_file} --installServer"
)
else: else:
server_command = ( server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M " f"java -Xms{Helpers.float_to_string(min_mem)}M "

View File

@ -202,12 +202,15 @@ class ServerInstance:
# remove the scheduled job since it's ran # remove the scheduled job since it's ran
return self.server_scheduler.remove_job(str(self.server_id)) return self.server_scheduler.remove_job(str(self.server_id))
def run_threaded_server(self, user_id): def run_threaded_server(self, user_id, forge_install=False):
# start the server # start the server
self.server_thread = threading.Thread( self.server_thread = threading.Thread(
target=self.start_server, target=self.start_server,
daemon=True, daemon=True,
args=(user_id,), args=(
user_id,
forge_install,
),
name=f"{self.server_id}_server_thread", name=f"{self.server_id}_server_thread",
) )
self.server_thread.start() self.server_thread.start()
@ -292,13 +295,13 @@ class ServerInstance:
logger.critical(f"Unable to write/access {self.server_path}") logger.critical(f"Unable to write/access {self.server_path}")
Console.critical(f"Unable to write/access {self.server_path}") Console.critical(f"Unable to write/access {self.server_path}")
def start_server(self, user_id): def start_server(self, user_id, forge_install=False):
if not user_id: if not user_id:
user_lang = self.helper.get_setting("language") user_lang = self.helper.get_setting("language")
else: else:
user_lang = HelperUsers.get_user_lang_by_id(user_id) user_lang = HelperUsers.get_user_lang_by_id(user_id)
if self.stats_helper.get_import_status(): if self.stats_helper.get_import_status() and not forge_install:
if user_id: if user_id:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
user_id, user_id,
@ -341,7 +344,9 @@ class ServerInstance:
"eula= true", "eula= true",
"eula =true", "eula =true",
] ]
# If this is a forge installer we're running we can bypass the eula checks.
if forge_install is True:
e_flag = True
if not e_flag and self.settings["type"] == "minecraft-java": if not e_flag and self.settings["type"] == "minecraft-java":
if user_id: if user_id:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
@ -422,6 +427,9 @@ class ServerInstance:
).format(self.name, ex) ).format(self.name, ex)
}, },
) )
if forge_install:
# Reset import status if failed while forge installing
self.stats_helper.finish_import()
return False return False
else: else:
@ -460,6 +468,9 @@ class ServerInstance:
).format(self.name, ex) ).format(self.name, ex)
}, },
) )
if forge_install:
# Reset import status if failed while forge installing
self.stats_helper.finish_import()
return False return False
out_buf = ServerOutBuf(self.helper, self.process, self.server_id) out_buf = ServerOutBuf(self.helper, self.process, self.server_id)
@ -540,6 +551,10 @@ class ServerInstance:
self.detect_crash, "interval", seconds=30, id=f"c_{self.server_id}" self.detect_crash, "interval", seconds=30, id=f"c_{self.server_id}"
) )
# If this is a forge install we'll call the watcher to do the things
if forge_install:
self.forge_install_watcher()
def check_internet_thread(self, user_id, user_lang): def check_internet_thread(self, user_id, user_lang):
if user_id: if user_id:
if not Helpers.check_internet(): if not Helpers.check_internet():
@ -553,6 +568,78 @@ class ServerInstance:
}, },
) )
def forge_install_watcher(self):
# Enter for install if that parameter is true
while True:
# We'll watch the process
if self.process.poll() is None:
# IF process still has not exited we'll keep looping
time.sleep(5)
Console.debug("Installing Forge...")
else:
# Process has exited. Lets do some work to setup the new
# run command.
# Let's grab the server object we're going to update.
server_obj = HelperServers.get_server_obj(self.server_id)
# The forge install is done so we can delete that install file.
os.remove(os.path.join(server_obj.path, server_obj.executable))
# We need to grab the exact forge version number.
# We know we can find it here in the run.sh/bat script.
run_file_path = ""
if self.helper.is_os_windows():
run_file_path = os.path.join(server_obj.path, "run.bat")
else:
run_file_path = os.path.join(server_obj.path, "run.sh")
if Helpers.check_file_perms(run_file_path) and os.path.isfile(
run_file_path
):
run_file = open(run_file_path, "r", encoding="utf-8")
run_file_text = run_file.read()
else:
Console.error(
"ERROR ! Forge install can't read the scripts files."
" Aborting ..."
)
return
# We get the server command parameters from forge script
server_command = re.findall(
r"java @([a-zA-Z0-9_\.]+)"
r" @([a-z.\/\-]+)([0-9.\-]+)\/\b([a-z_0-9]+\.txt)\b( .{2,4})?",
run_file_text,
)[0]
version = server_command[2]
executable_path = f"{server_command[1]}{server_command[2]}/"
# Let's set the proper server executable
server_obj.executable = os.path.join(
f"{executable_path}forge-{version}-server.jar"
)
# Now lets set up the new run command.
# This is based off the run.sh/bat that
# Forge uses in 1.16 and <
execution_command = (
f"java @{server_command[0]}"
f" @{executable_path}{server_command[3]} nogui {server_command[4]}"
)
server_obj.execution_command = execution_command
Console.debug("SUCCESS! Forge install completed")
# We'll update the server with the new information now.
HelperServers.update_server(server_obj)
self.stats_helper.finish_import()
server_users = PermissionsServers.get_server_user_list(self.server_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {}
)
break
def stop_crash_detection(self): def stop_crash_detection(self):
# This is only used if the crash detection settings change # This is only used if the crash detection settings change
# while the server is running. # while the server is running.

View File

@ -352,6 +352,38 @@ class AjaxHandler(BaseHandler):
self.controller.clear_unexecuted_commands() self.controller.clear_unexecuted_commands()
return return
elif page == "select_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
if photo == "login_1.jpg":
self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = f"{photo}"
else:
self.controller.management.set_login_image(f"custom/{photo}")
self.controller.cached_login = f"custom/{photo}"
return
elif page == "delete_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
if photo and photo != "login_1.jpg":
os.remove(
os.path.join(
self.controller.project_root,
f"app/frontend/static/assets/images/auth/custom/{photo}",
)
)
current = self.controller.cached_login
split = current.split("/")
if len(split) == 1:
current_photo = current
else:
current_photo = split[1]
if current_photo == photo:
self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = "login_1.jpg"
return
elif page == "kill": elif page == "kill":
if not permissions["Commands"] in user_perms: if not permissions["Commands"] in user_perms:
if not superuser: if not superuser:

View File

@ -849,6 +849,32 @@ 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"] = []
page_data["backgrounds"] = []
cached_split = self.controller.cached_login.split("/")
if len(cached_split) == 1:
page_data["backgrounds"].append(
self.controller.cached_login
)
else:
page_data["backgrounds"].append(cached_split[1])
if "login_1.jpg" not in page_data["backgrounds"]:
page_data["backgrounds"].append("login_1.jpg")
self.helper.ensure_dir_exists(
os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
)
for item in os.listdir(
os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
):
if item not in page_data["backgrounds"]:
page_data["backgrounds"].append(item)
page_data["background"] = self.controller.cached_login
else: else:
page_data["managed_users"] = self.controller.users.get_managed_users( page_data["managed_users"] = self.controller.users.get_managed_users(
exec_user["user_id"] exec_user["user_id"]

View File

@ -48,6 +48,7 @@ class PublicHandler(BaseHandler):
template = "public/404.html" template = "public/404.html"
if page == "login": if page == "login":
page_data["background"] = self.controller.cached_login
template = "public/login.html" template = "public/login.html"
elif page == 404: elif page == 404:

View File

@ -457,6 +457,9 @@ class ServerHandler(BaseHandler):
server = bleach.clean(self.get_argument("server", "")) server = bleach.clean(self.get_argument("server", ""))
server_name = bleach.clean(self.get_argument("server_name", "")) server_name = bleach.clean(self.get_argument("server_name", ""))
port = bleach.clean(self.get_argument("port", "")) port = bleach.clean(self.get_argument("port", ""))
if not port:
port = 19132
if int(port) < 1 or int(port) > 65535: if int(port) < 1 or int(port) > 65535:
self.redirect( self.redirect(
"/panel/error?error=Constraint Error: " "/panel/error?error=Constraint Error: "

View File

@ -152,65 +152,46 @@ class UploadHandler(BaseHandler):
return return
self.do_upload = True self.do_upload = True
if superuser: if not superuser:
exec_user_server_permissions = ( self.helper.websocket_helper.broadcast_user(
self.controller.server_perms.list_defined_permissions() user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"superError",
self.controller.users.get_user_lang_by_id(user_id),
),
},
) )
elif api_key is not None: return
exec_user_server_permissions = ( if not self.request.headers.get("X-Content-Type", None).startswith(
self.controller.server_perms.get_api_key_permissions_list( "image/"
api_key, server_id ):
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"fileError",
self.controller.users.get_user_lang_by_id(user_id),
),
},
) )
) return
else:
exec_user_server_permissions = (
self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
)
server_id = self.request.headers.get("X-ServerId", None)
if server_id is None:
logger.warning("Server ID not found in upload handler call")
Console.warning("Server ID not found in upload handler call")
self.do_upload = False
if user_id is None: if user_id is None:
logger.warning("User ID not found in upload handler call") logger.warning("User ID not found in upload handler call")
Console.warning("User ID not found in upload handler call") Console.warning("User ID not found in upload handler call")
self.do_upload = False self.do_upload = False
if EnumPermissionsServer.FILES not in exec_user_server_permissions: path = os.path.join(
logger.warning( self.controller.project_root,
f"User {user_id} tried to upload a file to " "app/frontend/static/assets/images/auth/custom",
f"{server_id} without permissions!"
) )
Console.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
self.do_upload = False
path = self.request.headers.get("X-Path", None)
filename = self.request.headers.get("X-FileName", None) filename = self.request.headers.get("X-FileName", None)
full_path = os.path.join(path, filename) full_path = os.path.join(path, filename)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
):
logger.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
Console.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
self.do_upload = False
if self.do_upload: if self.do_upload:
try: try:
self.f = open(full_path, "wb") self.f = open(full_path, "wb")

View File

@ -28362,11 +28362,6 @@ div.tagsinput span.tag a {
min-height: 100vh; min-height: 100vh;
} }
.auth.auth-bg-1 {
background: url("../../images/auth/login_1.jpg");
background-size: cover;
}
.auth.register-bg-1 { .auth.register-bg-1 {
background: url("../../images/auth/register.jpg") center center no-repeat; background: url("../../images/auth/register.jpg") center center no-repeat;
background-size: cover; background-size: cover;

View File

@ -26992,11 +26992,6 @@ div.tagsinput span.tag a {
min-height: 100vh; min-height: 100vh;
} }
.auth.auth-bg-1 {
background: url("../../images/auth/login_1.jpg");
background-size: cover;
}
.auth.register-bg-1 { .auth.register-bg-1 {
background: url("../../images/auth/register.jpg") center center no-repeat; background: url("../../images/auth/register.jpg") center center no-repeat;
background-size: cover; background-size: cover;

View File

@ -168,22 +168,11 @@
<td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist"> <td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% if server['user_command_permission'] %} {% if server['user_command_permission'] %}
{% if server['stats']['running'] %} {% if server['stats']['importing'] and server['stats']['running'] %}
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" data-toggle="tooltip" <!-- WHAT HAPPENED HERE -->
title="{{ translate('dashboard', 'stop' , data['lang']) }}"> <a data-id="{{server['server_data']['server_id']}}" class=""><i
<i class="fas fa-stop"></i> class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'installing',
</a> &nbsp; data['lang']) }}</i></a>
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% elif server['stats']['updating']%} {% elif server['stats']['updating']%}
<!-- WHAT HAPPENED HERE --> <!-- WHAT HAPPENED HERE -->
<a data-id="{{server['server_data']['server_id']}}" class=""><i <a data-id="{{server['server_data']['server_id']}}" class=""><i
@ -198,6 +187,21 @@
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i> <a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'importing', {{ translate('serverTerm', 'importing',
data['lang']) }}</a> data['lang']) }}</a>
{% elif server['stats']['running'] %}
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% else %} {% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip" <a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}"> title="{{ translate('dashboard', 'start' , data['lang']) }}">
@ -275,6 +279,8 @@
</td> </td>
<td draggable="false" id="server_running_status_{{server['server_data']['server_id']}}"> <td draggable="false" id="server_running_status_{{server['server_data']['server_id']}}">
<span class="port" data-toggle="tooltip" title="{{
server['server_data']['server_port'] }}">
{% if server['stats']['running'] %} {% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', <span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span> data['lang']) }}</span>
@ -286,11 +292,14 @@
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', <span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span> data['lang']) }}</span>
{% end %} {% end %}
<br />
<br />
</td> </td>
<span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}" <span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}"
data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"></span> data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"></span>
</tr> </tr>
{% end %} {% end %}
</span>
{% for server in data['failed_servers'] %} {% for server in data['failed_servers'] %}
<tr id="{{server['server_id']}}" draggable="false"> <tr id="{{server['server_id']}}" draggable="false">
<td class="text-warning"><i class="fas fa-server"></i>&nbsp;<a class="text-warning" <td class="text-warning"><i class="fas fa-server"></i>&nbsp;<a class="text-warning"

View File

@ -229,6 +229,69 @@
</div> </div>
</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>
{% end %} {% end %}
</div> </div>
</div> </div>
@ -279,6 +342,13 @@
$('.too_small2').popover("hide"); $('.too_small2').popover("hide");
} // New width } // New width
}); });
$('#file').change(function () {
console.log("File changed");
if ($('#file').val()) {
$('#upload-button').prop("disabled", false);
console.log("File changed good");
}
});
</script> </script>
<script> <script>
@ -307,6 +377,70 @@
}, },
}); });
}) })
$('.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>
{% end %} {% end %}

View File

@ -124,7 +124,7 @@ data['lang']) }}{% end %}
<div class="form-group"> <div class="form-group">
<label class="form-label" for="theme">{{ translate('userConfig', 'userTheme', data['lang']) <label class="form-label" for="theme">{{ translate('userConfig', 'userTheme', data['lang'])
}}</label> }}</label>
<select class="form-select form-control form-control-lg select-css" id="language" <select class="form-select form-control form-control-lg select-css" id="theme"
name="theme" form="user_form"> name="theme" form="user_form">
<option value="{{data['user'].get('theme', 'default')}}">{{data['user'].get('theme', 'default')}}</option> <option value="{{data['user'].get('theme', 'default')}}">{{data['user'].get('theme', 'default')}}</option>
{% for theme in data['themes'] %} {% for theme in data['themes'] %}

View File

@ -62,7 +62,21 @@
</span> </span>
</div> </div>
{% if data['permissions']['Commands'] in data['user_permissions'] %} {% if data['permissions']['Commands'] in data['user_permissions'] %}
{% if data['server_stats']['updating']%} {% if data['importing'] and data['server_stats']['running']%}
<div id="update_control_buttons"
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled"><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{translate('serverTerm', 'installing', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;"
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
}}</button>
</div>
{% elif data['server_stats']['updating']%}
<div id="update_control_buttons" <div id="update_control_buttons"
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
style="visibility: visible"> style="visibility: visible">

View File

@ -60,6 +60,11 @@
.login-input:focus { .login-input:focus {
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4); box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
} }
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}");
background-size: cover;
}
</style> </style>
{% if data['query'] %} {% if data['query'] %}
<form action="/public/login?{{ data['query'] }}" method="post"> <form action="/public/login?{{ data['query'] }}" method="post">

View File

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

View File

@ -97,7 +97,8 @@
"status": "Status", "status": "Status",
"stop": "Stop", "stop": "Stop",
"version": "Version", "version": "Version",
"welcome": "Welcome to Crafty Controller" "welcome": "Welcome to Crafty Controller",
"installing": "Installing..."
}, },
"datatables": { "datatables": {
"i18n": { "i18n": {
@ -181,7 +182,9 @@
"not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?", "not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?",
"portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.", "portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.",
"start-error": "Server {} failed to start with error code: {}", "start-error": "Server {} failed to start with error code: {}",
"terribleFailure": "What a Terrible Failure!" "terribleFailure": "What a Terrible Failure!",
"superError": "You must be a super user to complete this action.",
"fileError": "File type must be an image."
}, },
"footer": { "footer": {
"allRightsReserved": "All rights reserved", "allRightsReserved": "All rights reserved",
@ -224,7 +227,13 @@
"superConfirm": "Proceed only if you want this user to have access to EVERYTHING (all user accounts, servers, panel settings, etc.). They can even revoke your superuser rights.", "superConfirm": "Proceed only if you want this user to have access to EVERYTHING (all user accounts, servers, panel settings, etc.). They can even revoke your superuser rights.",
"superConfirmTitle": "Enable superuser? Are you sure?", "superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User", "user": "User",
"users": "Users" "users": "Users",
"loginImage": "Upload a background image for the login screen.",
"backgroundUpload": "Background Upload",
"loginBackground": "Login Background Image",
"select": "Select",
"selectImage": "Select an image",
"preview": "Preview"
}, },
"rolesConfig": { "rolesConfig": {
"config": "Role Config", "config": "Role Config",
@ -481,7 +490,8 @@
"starting": "Delayed-Start", "starting": "Delayed-Start",
"stop": "Stop", "stop": "Stop",
"stopScroll": "Stop Auto Scrolling", "stopScroll": "Stop Auto Scrolling",
"updating": "Updating..." "updating": "Updating...",
"installing": "Installing..."
}, },
"serverMetrics": { "serverMetrics": {
"resetZoom": "Reset Zoom", "resetZoom": "Reset Zoom",

View File

@ -97,7 +97,8 @@
"status": "Statut", "status": "Statut",
"stop": "Arrêter", "stop": "Arrêter",
"version": "Version", "version": "Version",
"welcome": "Bienvenue sur Crafty Controller" "welcome": "Bienvenue sur Crafty Controller",
"installing": "Installation ..."
}, },
"datatables": { "datatables": {
"i18n": { "i18n": {
@ -450,7 +451,13 @@
"starting": "Démarrage retardé", "starting": "Démarrage retardé",
"stop": "Arrêter", "stop": "Arrêter",
"stopScroll": "Stopper l'Auto Défilement", "stopScroll": "Stopper l'Auto Défilement",
"updating": "Mise à Jour ..." "updating": "Mise à Jour ...",
"installing": "Installation ..."
},
"serverMetrics": {
"resetZoom": "Reset Zoom",
"zoomHint1": "Pour zoomer le graphique, maintenir la touche SHIFT et utiliser la molette de la souris.",
"zoomHint2": "Il est aussi possible de maintenir la touche SHIFT puis de clicker et déplacer la zone sur laquelle zoomer."
}, },
"serverWizard": { "serverWizard": {
"absoluteServerPath": "Chemin absolu de Votre Serveur", "absoluteServerPath": "Chemin absolu de Votre Serveur",