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/
|
.idea/
|
||||||
/imports/
|
/imports/
|
||||||
/servers/
|
/servers/
|
||||||
|
/app/frontend/static/assets/images/auth/custom/
|
||||||
/backups/
|
/backups/
|
||||||
/temp/
|
/temp/
|
||||||
/docker/servers/
|
/docker/servers/
|
||||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
|
@ -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 "
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
|
@ -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"]
|
||||||
|
@ -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:
|
||||||
|
@ -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: "
|
||||||
|
@ -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")
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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> {{ translate('serverTerm', 'installing',
|
||||||
</a>
|
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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{% 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>
|
||||||
|
|
||||||
|
<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 %}
|
{% 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> <a class="text-warning"
|
<td class="text-warning"><i class="fas fa-server"></i> <a class="text-warning"
|
||||||
|
@ -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%"> <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 %}
|
@ -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'] %}
|
||||||
|
@ -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> {{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">
|
||||||
|
@ -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">
|
||||||
|
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",
|
"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",
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user