mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into feature/steamcmd
This commit is contained in:
commit
7b07ac088b
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,6 +20,7 @@ venv.bak/
|
||||
.idea/
|
||||
/imports/
|
||||
/servers/
|
||||
/app/frontend/static/assets/images/auth/custom/
|
||||
/backups/
|
||||
/temp/
|
||||
/docker/servers/
|
||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,13 +1,14 @@
|
||||
# Changelog
|
||||
## --- [4.0.17] - 2022/TBD
|
||||
## --- [4.0.17] - 2022/11/30
|
||||
### 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
|
||||
TBD
|
||||
- Fix no port on bedrock server creation. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/493))
|
||||
### Tweaks
|
||||
TBD
|
||||
### Lang
|
||||
TBD
|
||||
- Docker🐋 | Update image base to Ubuntu 22.04 Jammy ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/497))<br>
|
||||
*(OpenJDK16 Removed, no jammy backport)*
|
||||
<br><br>
|
||||
|
||||
## --- [4.0.16] - 2022/10/23
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND="noninteractive"
|
||||
|
||||
@ -24,7 +24,6 @@ RUN apt-get update \
|
||||
default-jre \
|
||||
openjdk-8-jre-headless \
|
||||
openjdk-11-jre-headless \
|
||||
openjdk-16-jre-headless \
|
||||
openjdk-17-jre-headless \
|
||||
&& apt-get autoremove \
|
||||
&& apt-get clean
|
||||
|
@ -94,6 +94,14 @@ class ManagementController:
|
||||
def 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
|
||||
def update_scheduled_task(schedule_id, updates):
|
||||
return HelpersManagement.update_scheduled_task(schedule_id, updates)
|
||||
|
@ -119,9 +119,15 @@ class ServersController(metaclass=Singleton):
|
||||
return srv.stats_helper.set_import()
|
||||
|
||||
@staticmethod
|
||||
def finish_import(server_id):
|
||||
def finish_import(server_id, forge=False):
|
||||
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
|
||||
def get_import_status(server_id):
|
||||
|
@ -187,24 +187,22 @@ class ServerJars:
|
||||
|
||||
# open a file stream
|
||||
with requests.get(fetch_url, timeout=2, stream=True) as r:
|
||||
success = False
|
||||
try:
|
||||
with open(path, "wb") as 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)
|
||||
|
||||
for user in server_users:
|
||||
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
|
||||
success = True
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to save jar to {path} due to error:{e}")
|
||||
ServersController.finish_import(server_id)
|
||||
server_users = PermissionsServers.get_server_user_list(server_id)
|
||||
|
||||
for user in server_users:
|
||||
self.helper.websocket_helper.broadcast_user(
|
||||
user, "notification", "Executable download finished"
|
||||
@ -213,5 +211,4 @@ class ServerJars:
|
||||
self.helper.websocket_helper.broadcast_user(
|
||||
user, "send_start_reload", {}
|
||||
)
|
||||
|
||||
return False
|
||||
return success
|
||||
|
@ -43,6 +43,7 @@ class AuditLog(BaseModel):
|
||||
# **********************************************************************************
|
||||
class CraftySettings(BaseModel):
|
||||
secret_api_key = CharField(default="")
|
||||
login_photo = CharField(default="login_1.jpg")
|
||||
|
||||
class Meta:
|
||||
table_name = "crafty_settings"
|
||||
@ -254,6 +255,19 @@ class HelpersManagement:
|
||||
)
|
||||
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
|
||||
# **********************************************************************************
|
||||
|
@ -75,6 +75,7 @@ class Controller:
|
||||
timezone=str(tz)
|
||||
)
|
||||
self.first_login = False
|
||||
self.cached_login = self.management.get_login_image()
|
||||
self.support_scheduler.start()
|
||||
|
||||
@staticmethod
|
||||
@ -486,11 +487,26 @@ class Controller:
|
||||
return False
|
||||
|
||||
if Helpers.is_os_windows():
|
||||
# Let's check for and setup for install server commands
|
||||
if server == "forge":
|
||||
server_command = (
|
||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||
f'-jar "{server_file}" --installServer'
|
||||
)
|
||||
else:
|
||||
server_command = (
|
||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||
f'-jar "{server_file}" nogui'
|
||||
)
|
||||
else:
|
||||
if server == "forge":
|
||||
server_command = (
|
||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||
f"-jar {server_file} --installServer"
|
||||
)
|
||||
else:
|
||||
server_command = (
|
||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||
|
@ -202,12 +202,15 @@ class ServerInstance:
|
||||
# remove the scheduled job since it's ran
|
||||
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
|
||||
self.server_thread = threading.Thread(
|
||||
target=self.start_server,
|
||||
daemon=True,
|
||||
args=(user_id,),
|
||||
args=(
|
||||
user_id,
|
||||
forge_install,
|
||||
),
|
||||
name=f"{self.server_id}_server_thread",
|
||||
)
|
||||
self.server_thread.start()
|
||||
@ -292,13 +295,13 @@ class ServerInstance:
|
||||
logger.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:
|
||||
user_lang = self.helper.get_setting("language")
|
||||
else:
|
||||
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:
|
||||
self.helper.websocket_helper.broadcast_user(
|
||||
user_id,
|
||||
@ -341,7 +344,9 @@ class ServerInstance:
|
||||
"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 user_id:
|
||||
self.helper.websocket_helper.broadcast_user(
|
||||
@ -422,6 +427,9 @@ class ServerInstance:
|
||||
).format(self.name, ex)
|
||||
},
|
||||
)
|
||||
if forge_install:
|
||||
# Reset import status if failed while forge installing
|
||||
self.stats_helper.finish_import()
|
||||
return False
|
||||
|
||||
else:
|
||||
@ -460,6 +468,9 @@ class ServerInstance:
|
||||
).format(self.name, ex)
|
||||
},
|
||||
)
|
||||
if forge_install:
|
||||
# Reset import status if failed while forge installing
|
||||
self.stats_helper.finish_import()
|
||||
return False
|
||||
|
||||
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}"
|
||||
)
|
||||
|
||||
# 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):
|
||||
if user_id:
|
||||
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):
|
||||
# This is only used if the crash detection settings change
|
||||
# while the server is running.
|
||||
|
@ -352,6 +352,38 @@ class AjaxHandler(BaseHandler):
|
||||
self.controller.clear_unexecuted_commands()
|
||||
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":
|
||||
if not permissions["Commands"] in user_perms:
|
||||
if not superuser:
|
||||
|
@ -849,6 +849,32 @@ class PanelHandler(BaseHandler):
|
||||
page_data["roles"] = self.controller.roles.get_all_roles()
|
||||
page_data["auth-servers"][user.user_id] = super_auth_servers
|
||||
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:
|
||||
page_data["managed_users"] = self.controller.users.get_managed_users(
|
||||
exec_user["user_id"]
|
||||
|
@ -48,6 +48,7 @@ class PublicHandler(BaseHandler):
|
||||
template = "public/404.html"
|
||||
|
||||
if page == "login":
|
||||
page_data["background"] = self.controller.cached_login
|
||||
template = "public/login.html"
|
||||
|
||||
elif page == 404:
|
||||
|
@ -457,6 +457,9 @@ class ServerHandler(BaseHandler):
|
||||
server = bleach.clean(self.get_argument("server", ""))
|
||||
server_name = bleach.clean(self.get_argument("server_name", ""))
|
||||
port = bleach.clean(self.get_argument("port", ""))
|
||||
|
||||
if not port:
|
||||
port = 19132
|
||||
if int(port) < 1 or int(port) > 65535:
|
||||
self.redirect(
|
||||
"/panel/error?error=Constraint Error: "
|
||||
|
@ -152,65 +152,46 @@ class UploadHandler(BaseHandler):
|
||||
return
|
||||
self.do_upload = True
|
||||
|
||||
if superuser:
|
||||
exec_user_server_permissions = (
|
||||
self.controller.server_perms.list_defined_permissions()
|
||||
if not superuser:
|
||||
self.helper.websocket_helper.broadcast_user(
|
||||
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:
|
||||
exec_user_server_permissions = (
|
||||
self.controller.server_perms.get_api_key_permissions_list(
|
||||
api_key, server_id
|
||||
return
|
||||
if not self.request.headers.get("X-Content-Type", None).startswith(
|
||||
"image/"
|
||||
):
|
||||
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),
|
||||
),
|
||||
},
|
||||
)
|
||||
)
|
||||
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
|
||||
|
||||
return
|
||||
if user_id is None:
|
||||
logger.warning("User ID not found in upload handler call")
|
||||
Console.warning("User ID not found in upload handler call")
|
||||
self.do_upload = False
|
||||
|
||||
if EnumPermissionsServer.FILES not in exec_user_server_permissions:
|
||||
logger.warning(
|
||||
f"User {user_id} tried to upload a file to "
|
||||
f"{server_id} without permissions!"
|
||||
path = os.path.join(
|
||||
self.controller.project_root,
|
||||
"app/frontend/static/assets/images/auth/custom",
|
||||
)
|
||||
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)
|
||||
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:
|
||||
try:
|
||||
self.f = open(full_path, "wb")
|
||||
|
@ -28362,11 +28362,6 @@ div.tagsinput span.tag a {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.auth.auth-bg-1 {
|
||||
background: url("../../images/auth/login_1.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.auth.register-bg-1 {
|
||||
background: url("../../images/auth/register.jpg") center center no-repeat;
|
||||
background-size: cover;
|
||||
|
@ -26992,11 +26992,6 @@ div.tagsinput span.tag a {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.auth.auth-bg-1 {
|
||||
background: url("../../images/auth/login_1.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.auth.register-bg-1 {
|
||||
background: url("../../images/auth/register.jpg") center center no-repeat;
|
||||
background-size: cover;
|
||||
|
@ -168,22 +168,11 @@
|
||||
|
||||
<td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
|
||||
{% if server['user_command_permission'] %}
|
||||
{% if 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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
{% if server['stats']['importing'] and server['stats']['running'] %}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<a data-id="{{server['server_data']['server_id']}}" class=""><i
|
||||
class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'installing',
|
||||
data['lang']) }}</i></a>
|
||||
{% elif server['stats']['updating']%}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<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>
|
||||
{{ translate('serverTerm', 'importing',
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
{% else %}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip"
|
||||
title="{{ translate('dashboard', 'start' , data['lang']) }}">
|
||||
@ -275,6 +279,8 @@
|
||||
|
||||
</td>
|
||||
<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'] %}
|
||||
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
|
||||
data['lang']) }}</span>
|
||||
@ -286,11 +292,14 @@
|
||||
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
|
||||
data['lang']) }}</span>
|
||||
{% end %}
|
||||
<br />
|
||||
<br />
|
||||
</td>
|
||||
<span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}"
|
||||
data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"></span>
|
||||
</tr>
|
||||
{% end %}
|
||||
</span>
|
||||
{% for server in data['failed_servers'] %}
|
||||
<tr id="{{server['server_id']}}" draggable="false">
|
||||
<td class="text-warning"><i class="fas fa-server"></i> <a class="text-warning"
|
||||
|
@ -229,6 +229,69 @@
|
||||
</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 %}
|
||||
</div>
|
||||
</div>
|
||||
@ -279,6 +342,13 @@
|
||||
$('.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>
|
||||
|
||||
@ -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%"> <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 %}
|
@ -124,7 +124,7 @@ data['lang']) }}{% end %}
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="theme">{{ translate('userConfig', 'userTheme', data['lang'])
|
||||
}}</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">
|
||||
<option value="{{data['user'].get('theme', 'default')}}">{{data['user'].get('theme', 'default')}}</option>
|
||||
{% for theme in data['themes'] %}
|
||||
|
@ -62,7 +62,21 @@
|
||||
</span>
|
||||
</div>
|
||||
{% 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> {{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"
|
||||
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">
|
||||
|
@ -60,6 +60,11 @@
|
||||
.login-input:focus {
|
||||
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>
|
||||
{% if data['query'] %}
|
||||
<form action="/public/login?{{ data['query'] }}" method="post">
|
||||
|
18
app/migrations/20221109_login_photo.py
Normal file
18
app/migrations/20221109_login_photo.py
Normal 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.
|
||||
"""
|
@ -97,7 +97,8 @@
|
||||
"status": "Status",
|
||||
"stop": "Stop",
|
||||
"version": "Version",
|
||||
"welcome": "Welcome to Crafty Controller"
|
||||
"welcome": "Welcome to Crafty Controller",
|
||||
"installing": "Installing..."
|
||||
},
|
||||
"datatables": {
|
||||
"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?",
|
||||
"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: {}",
|
||||
"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": {
|
||||
"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.",
|
||||
"superConfirmTitle": "Enable superuser? Are you sure?",
|
||||
"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": {
|
||||
"config": "Role Config",
|
||||
@ -481,7 +490,8 @@
|
||||
"starting": "Delayed-Start",
|
||||
"stop": "Stop",
|
||||
"stopScroll": "Stop Auto Scrolling",
|
||||
"updating": "Updating..."
|
||||
"updating": "Updating...",
|
||||
"installing": "Installing..."
|
||||
},
|
||||
"serverMetrics": {
|
||||
"resetZoom": "Reset Zoom",
|
||||
|
@ -97,7 +97,8 @@
|
||||
"status": "Statut",
|
||||
"stop": "Arrêter",
|
||||
"version": "Version",
|
||||
"welcome": "Bienvenue sur Crafty Controller"
|
||||
"welcome": "Bienvenue sur Crafty Controller",
|
||||
"installing": "Installation ..."
|
||||
},
|
||||
"datatables": {
|
||||
"i18n": {
|
||||
@ -450,7 +451,13 @@
|
||||
"starting": "Démarrage retardé",
|
||||
"stop": "Arrêter",
|
||||
"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": {
|
||||
"absoluteServerPath": "Chemin absolu de Votre Serveur",
|
||||
|
Loading…
Reference in New Issue
Block a user