Merge branch 'dev' into feature/steamcmd

This commit is contained in:
amcmanu3 2023-08-04 15:34:11 -04:00
commit 7c2ce9dcc5
73 changed files with 2743 additions and 2072 deletions

View File

@ -3,6 +3,7 @@ docker/
.dockerignore .dockerignore
Dockerfile Dockerfile
docker-compose.yml docker-compose.yml
docker-compose.yml.example
# git & gitlab related # git & gitlab related
.git/ .git/
@ -17,6 +18,8 @@ docker-compose.yml
.venv .venv
.vscode .vscode
crafty_commander.exe crafty_commander.exe
CHANGELOG.md
CONTRIBUTING.md
DBCHANGES.md DBCHANGES.md
docker-compose.yml.example
README.md README.md
sonar-project.properties

View File

@ -28,7 +28,7 @@ docker-build-dev:
docker version docker version
- docker run --rm --privileged aptman/qus -- -r - docker run --rm --privileged aptman/qus -- -r
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64 - docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY - echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY - echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
script: script:
- | - |
@ -45,6 +45,7 @@ docker-build-dev:
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")" --build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}" --build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}" --build-arg "CRAFTY_VER=${VERSION}"
--provenance false
--tag "$CI_REGISTRY_IMAGE${tag}" --tag "$CI_REGISTRY_IMAGE${tag}"
--tag "arcadiatechnology/crafty-4${tag}" --tag "arcadiatechnology/crafty-4${tag}"
--platform linux/arm64/v8,linux/amd64 --platform linux/arm64/v8,linux/amd64
@ -84,7 +85,7 @@ docker-build-prod:
docker version docker version
- docker run --rm --privileged aptman/qus -- -r - docker run --rm --privileged aptman/qus -- -r
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64 - docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY - echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY - echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
script: script:
- | - |
@ -100,6 +101,7 @@ docker-build-prod:
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")" --build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}" --build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}" --build-arg "CRAFTY_VER=${VERSION}"
--provenance false
--tag "$CI_REGISTRY_IMAGE:$VERSION" --tag "$CI_REGISTRY_IMAGE:$VERSION"
--tag "$CI_REGISTRY_IMAGE:latest" --tag "$CI_REGISTRY_IMAGE:latest"
--tag "arcadiatechnology/crafty-4:$VERSION" --tag "arcadiatechnology/crafty-4:$VERSION"

View File

@ -57,3 +57,27 @@ pylint:
reports: reports:
codequality: codeclimate.json codequality: codeclimate.json
when: always when: always
# SonarQube/SonarCloud - Code Climate & QA [https://www.sonarsource.com]
sonarcloud-check:
stage: lint
image:
name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""]
tags:
- docker
rules:
- if: "$SONAR_TOKEN == null"
when: never
- if: "$CODE_QUALITY_DISABLED"
when: never
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- sonar-scanner

View File

@ -606,5 +606,5 @@ preferred-modules=
# Exceptions that will emit a warning when being caught. Defaults to # Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception". # "BaseException, Exception".
overgeneral-exceptions=BaseException, overgeneral-exceptions=builtins.BaseException,
Exception builtins.Exception

View File

@ -1,5 +1,5 @@
# Changelog # Changelog
## --- [4.0.23] - 2023/TBD ## --- [4.1.4] - 2023/TBD
### New features ### New features
TBD TBD
### Bug fixes ### Bug fixes
@ -10,6 +10,64 @@ TBD
TBD TBD
<br><br> <br><br>
## --- [4.1.3] - 2023/07/18
### Bug fixes
- Include tzdata in Docker image ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/604))
- Fix text/formatting issue on server config page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/602))
- Bump required version of PyYAML to 6.0.1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/609))
- Fix enable/disable schedule toggles on schedule list ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/606))
- Fix formatting on Creation page when server jars is unavailable ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/601))
### Refactor
- Replace "in_file" helper method ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/605))
### Tweaks
- Add public status link to login ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/608))
<br><br>
## --- [4.1.2] - 2023/06/18
### Bug fixes
- Fix upload root files being hidden ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/590))
- Send empty json for no banned/cached players ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/589))
- Bump Tornado from 6.0 to 6.3.2 in response to CVE-2023-28370 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/591))
- Fix bug where commands would show "command_server" when initially created ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/592))
- Add ID autofield to management CraftySettings class ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/599))
### Refactor
- Optimize player management page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/593))
### Tweaks
- Remove bedrock servers in serverjars options ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/595))
- Bump cryptography & pyOpenSSL ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/596))
- Bump requests ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/600))
### Lang
- Update es_ES & pl_PL lang, thank you `.lucyy_` & `terrariadlc` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/597))
<br><br>
## --- [4.1.1] - 2023/05/23
### Bug fixes
- Fix task scheduling where a command was not sent to the DB ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/586))
### Tweaks
- Improve the UI on several areas of the Crafty Panel ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/547))
- Improve creation page errors / Server Jars Credit ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/587))
<br><br>
## --- [4.1.0] - 2023/05/15
### New features
- Mobile PWA App (beta) | Ability to add a Crafty icon to your mobile's home screen ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/576))
- [New Crafty Documentation release](https://docs.craftycontrol.com)
### Refactor
- Frontend Ajax Refactor | Start using API to send Remote Comms to Server ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/565))
- MKDocs Release | Replace wiki names with docs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/583))
### Bug fixes
- Fix pipelines failing to build from gitlab pre-defined variable deprecation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/582))
- Fix incompatible buildx provenance meta, causing digest issues on GL/DH container registries ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/582))
- Fix Auth'd servers in roles | Refine server ordering ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/574))
- Fix import loop detection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/575))
- Fix Cargo errors on Ubuntu 23.04 installs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/579))
- Fix project root error on first start ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/580))
### Tweaks
- Check for python version so we don't just fail out on unsupported python versions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/577))
- Show warning for serverjars API connection issues ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/581))
- Retain pathing in execution command on backup restore ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/578))
<br><br>
## --- [4.0.22] - 2023/04/08 ## --- [4.0.22] - 2023/04/08
### Bug fixes ### Bug fixes
- Fix dashboard crash for users without disks or if crafty doesn't have permission to access mount point ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/571)) - Fix dashboard crash for users without disks or if crafty doesn't have permission to access mount point ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/571))

View File

@ -26,6 +26,7 @@ RUN apt-get update \
openjdk-11-jre-headless \ openjdk-11-jre-headless \
openjdk-17-jre-headless \ openjdk-17-jre-headless \
lib32stdc++6 \ lib32stdc++6 \
tzdata \
&& apt-get autoremove \ && apt-get autoremove \
&& apt-get clean && apt-get clean
@ -68,7 +69,7 @@ LABEL \
org.opencontainers.image.title="Crafty Controller" \ org.opencontainers.image.title="Crafty Controller" \
org.opencontainers.image.description="A Game Server Control Panel / Launcher" \ org.opencontainers.image.description="A Game Server Control Panel / Launcher" \
org.opencontainers.image.url="https://craftycontrol.com/" \ org.opencontainers.image.url="https://craftycontrol.com/" \
org.opencontainers.image.documentation="https://wiki.craftycontrol.com/" \ org.opencontainers.image.documentation="https://docs.craftycontrol.com" \
org.opencontainers.image.source="https://gitlab.com/crafty-controller/crafty-4" \ org.opencontainers.image.source="https://gitlab.com/crafty-controller/crafty-4" \
org.opencontainers.image.vendor="Arcadia Technology, LLC." \ org.opencontainers.image.vendor="Arcadia Technology, LLC." \
org.opencontainers.image.licenses="GPL-3.0" org.opencontainers.image.licenses="GPL-3.0"

View File

@ -1,5 +1,5 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com) [![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
# Crafty Controller 4.0.23 # Crafty Controller 4.1.4
> Python based Control Panel for your Minecraft Server > Python based Control Panel for your Minecraft Server
## What is Crafty Controller? ## What is Crafty Controller?
@ -9,7 +9,7 @@ a web interface for the server administrators to interact with their servers. Cr
is compatible with Docker, Linux, Windows 7, Windows 8 and Windows 10. is compatible with Docker, Linux, Windows 7, Windows 8 and Windows 10.
## Documentation ## Documentation
Documentation available on [wiki.craftycontrol.com](https://craftycontrol.com) Documentation available on [Crafty Docs](https://docs.craftycontrol.com)
## Meta ## Meta
Project Homepage - https://craftycontrol.com Project Homepage - https://craftycontrol.com

View File

@ -255,6 +255,7 @@ class ServersController(metaclass=Singleton):
@staticmethod @staticmethod
def get_authorized_servers(user_id): def get_authorized_servers(user_id):
server_ids = []
server_data: t.List[t.Dict[str, t.Any]] = [] server_data: t.List[t.Dict[str, t.Any]] = []
user_roles = HelperUsers.user_role_query(user_id) user_roles = HelperUsers.user_role_query(user_id)
for user in user_roles: for user in user_roles:
@ -262,11 +263,13 @@ class ServersController(metaclass=Singleton):
user.role_id user.role_id
) )
for role in role_servers: for role in role_servers:
server_data.append( if role.server_id.server_id not in server_ids:
ServersController().get_server_instance_by_id( server_ids.append(role.server_id.server_id)
role.server_id.server_id server_data.append(
ServersController().get_server_instance_by_id(
role.server_id.server_id
)
) )
)
return server_data return server_data
@ -277,11 +280,10 @@ class ServersController(metaclass=Singleton):
for role in roles_list: for role in roles_list:
role_users = HelperUsers.get_users_from_role(role.role_id) role_users = HelperUsers.get_users_from_role(role.role_id)
for user_role in role_users: for user_role in role_users:
user_ids.add(user_role.user_id) user_ids.add(user_role.user_id.user_id)
for user_id in HelperUsers.get_super_user_list(): for user_id in HelperUsers.get_super_user_list():
user_ids.add(user_id) user_ids.add(user_id)
return user_ids return user_ids
def get_all_servers_stats(self): def get_all_servers_stats(self):
@ -515,6 +517,25 @@ class ServersController(metaclass=Singleton):
# ********************************************************************************** # **********************************************************************************
# Servers Helpers Methods # Servers Helpers Methods
# ********************************************************************************** # **********************************************************************************
@staticmethod
def get_cached_players(server_id):
srv = ServersController().get_server_instance_by_id(server_id)
stats = srv.stats_helper.get_server_stats()
server_path = stats["server_id"]["path"]
path = os.path.join(server_path, "usercache.json")
try:
with open(
Helpers.get_os_understandable_path(path), encoding="utf-8"
) as file:
content = file.read()
file.close()
except Exception as ex:
logger.error(ex)
return {}
return json.loads(content)
@staticmethod @staticmethod
def get_banned_players(server_id): def get_banned_players(server_id):
srv = ServersController().get_server_instance_by_id(server_id) srv = ServersController().get_server_instance_by_id(server_id)
@ -529,8 +550,8 @@ class ServersController(metaclass=Singleton):
content = file.read() content = file.read()
file.close() file.close()
except Exception as ex: except Exception as ex:
print(ex) logger.error(ex)
return None return {}
return json.loads(content) return json.loads(content)

View File

@ -89,6 +89,7 @@ class UsersController:
}, },
}, },
"hints": {"type": "boolean"}, "hints": {"type": "boolean"},
"server_order": {"type": "string"},
} }
# ********************************************************************************** # **********************************************************************************

View File

@ -153,6 +153,9 @@ class ServerJars:
def _get_server_type_list(self): def _get_server_type_list(self):
url = "/api/fetchTypes/" url = "/api/fetchTypes/"
response = self._get_api_result(url) response = self._get_api_result(url)
if "bedrock" in response.keys():
# remove pocketmine from options
del response["bedrock"]
return response return response
def download_jar(self, jar, server, version, path, server_id): def download_jar(self, jar, server, version, path, server_id):

View File

@ -43,6 +43,7 @@ class AuditLog(BaseModel):
# Crafty Settings Class # Crafty Settings Class
# ********************************************************************************** # **********************************************************************************
class CraftySettings(BaseModel): class CraftySettings(BaseModel):
id = AutoField()
secret_api_key = CharField(default="") secret_api_key = CharField(default="")
cookie_secret = CharField(default="") cookie_secret = CharField(default="")
login_photo = CharField(default="login_1.jpg") login_photo = CharField(default="login_1.jpg")

View File

@ -386,7 +386,7 @@ class HelperUsers:
@staticmethod @staticmethod
def get_users_from_role(role_id): def get_users_from_role(role_id):
UserRoles.select().where(UserRoles.role_id == role_id).execute() return UserRoles.select().where(UserRoles.role_id == role_id).execute()
# ********************************************************************************** # **********************************************************************************
# ApiKeys Methods # ApiKeys Methods

View File

@ -16,6 +16,7 @@ import zipfile
import pathlib import pathlib
import ctypes import ctypes
import shutil import shutil
import shlex
import subprocess import subprocess
import itertools import itertools
from datetime import datetime from datetime import datetime
@ -148,6 +149,29 @@ class Helpers:
logger.error(f"Unable to resolve remote bedrock download url! \n{e}") logger.error(f"Unable to resolve remote bedrock download url! \n{e}")
return False return False
def get_execution_java(self, value, execution_command):
if self.is_os_windows():
execution_list = shlex.split(execution_command, posix=False)
else:
execution_list = shlex.split(execution_command, posix=True)
if (
not any(value in path for path in self.find_java_installs())
and value != "java"
):
return
if value != "java":
if self.is_os_windows():
execution_list[0] = '"' + value + '/bin/java"'
else:
execution_list[0] = '"' + value + '"'
else:
execution_list[0] = "java"
execution_command = ""
for item in execution_list:
execution_command += item + " "
return execution_command
def detect_java(self): def detect_java(self):
if len(self.find_java_installs()) > 0: if len(self.find_java_installs()) > 0:
return True return True
@ -302,6 +326,16 @@ class Helpers:
except Exception: except Exception:
return False return False
@staticmethod
def check_address_status(address):
try:
response = requests.get(address, timeout=2)
return (
response.status_code // 100 == 2
) # Check if the status code starts with 2
except requests.RequestException:
return False
@staticmethod @staticmethod
def check_port(server_port): def check_port(server_port):
try: try:
@ -474,9 +508,9 @@ class Helpers:
return mounts return mounts
def is_subdir(self, server_path, root_dir): def is_subdir(self, child_path, parent_path):
server_path = os.path.realpath(server_path) server_path = os.path.realpath(child_path)
root_dir = os.path.realpath(root_dir) root_dir = os.path.realpath(parent_path)
if self.is_os_windows(): if self.is_os_windows():
try: try:
@ -1211,22 +1245,6 @@ class Helpers:
return temp_dir return temp_dir
return False return False
@staticmethod
def in_path(parent_path, child_path):
# Smooth out relative path names, note: if you are concerned about
# symbolic links, you should use os.path.realpath too
parent_path = os.path.abspath(parent_path)
child_path = os.path.abspath(child_path)
# Compare the common path of the parent and child path with the
# common path of just the parent path. Using the commonpath method
# on just the parent path will regularise the path name in the same way
# as the comparison that deals with both paths, removing any trailing
# path separator
return os.path.commonpath([parent_path]) == os.path.commonpath(
[parent_path, child_path]
)
@staticmethod @staticmethod
def download_file(executable_url, jar_path): def download_file(executable_url, jar_path):
try: try:
@ -1261,3 +1279,24 @@ class Helpers:
if region == "EN": if region == "EN":
return "en" return "en"
return lang + "-" + region return lang + "-" + region
@staticmethod
def get_player_avatar(uuid_player):
mojang_response = requests.get(
f"https://sessionserver.mojang.com/session/minecraft/profile/{uuid_player}",
timeout=10,
)
if mojang_response.status_code == 200:
uuid_profile = mojang_response.json()
profile_properties = uuid_profile["properties"]
for prop in profile_properties:
if prop["name"] == "textures":
decoded_bytes = base64.b64decode(prop["value"])
decoded_str = decoded_bytes.decode("utf-8")
texture_json = json.loads(decoded_str)
skin_url = texture_json["textures"]["SKIN"]["url"]
skin_response = requests.get(skin_url, stream=True, timeout=10)
if skin_response.status_code == 200:
return base64.b64encode(skin_response.content)
else:
return

View File

@ -11,6 +11,7 @@ import subprocess
import html import html
import urllib.request import urllib.request
import glob import glob
import json
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
@ -132,6 +133,15 @@ class ServerInstance:
self.server_object = HelperServers.get_server_obj(self.server_id) self.server_object = HelperServers.get_server_obj(self.server_id)
self.stats_helper = HelperServerStats(self.server_id) self.stats_helper = HelperServerStats(self.server_id)
self.last_backup_failed = False self.last_backup_failed = False
try:
with open(
os.path.join(self.server_object.path, "db_stats", "players_cache.json"),
"r",
encoding="utf-8",
) as f:
self.player_cache = list(json.load(f).values())
except:
self.player_cache = []
try: try:
self.tz = get_localzone() self.tz = get_localzone()
except ZoneInfoNotFoundError as e: except ZoneInfoNotFoundError as e:
@ -799,6 +809,7 @@ class ServerInstance:
) )
if self.settings["stop_command"]: if self.settings["stop_command"]:
self.send_command(self.settings["stop_command"]) self.send_command(self.settings["stop_command"])
self.write_player_cache()
else: else:
# windows will need to be handled separately for Ctrl+C # windows will need to be handled separately for Ctrl+C
self.process.terminate() self.process.terminate()
@ -1247,6 +1258,40 @@ class ServerInstance:
) )
update_thread.start() update_thread.start()
def write_player_cache(self):
write_json = {}
for item in self.player_cache:
write_json[item["name"]] = item
with open(
os.path.join(self.server_path, "db_stats", "players_cache.json"),
"w",
encoding="utf-8",
) as f:
f.write(json.dumps(write_json, indent=4))
logger.info("Cache file refreshed")
def cache_players(self):
server_players = self.get_server_players()
for p in self.player_cache[:]:
if p["status"] == "Online" and p["name"] not in server_players:
p["status"] = "Offline"
p["last_seen"] = datetime.datetime.now().strftime("%d/%m/%Y %H:%M")
elif p["name"] in server_players:
self.player_cache.remove(p)
for player in server_players:
if player == "Anonymous Player":
# Skip Anonymous Player
continue
if player in self.player_cache:
self.player_cache.remove(player)
self.player_cache.append(
{
"name": player,
"status": "Online",
"last_seen": datetime.datetime.now().strftime("%d/%m/%Y %H:%M"),
}
)
def check_update(self): def check_update(self):
return self.stats_helper.get_server_stats()["updating"] return self.stats_helper.get_server_stats()["updating"]
@ -1433,6 +1478,12 @@ class ServerInstance:
minutes=self.helper.get_setting("dir_size_poll_freq_minutes"), minutes=self.helper.get_setting("dir_size_poll_freq_minutes"),
id=str(self.server_id) + "_dir_poll", id=str(self.server_id) + "_dir_poll",
) )
self.dir_scheduler.add_job(
self.cache_players,
"interval",
seconds=5,
id=str(self.server_id) + "_players_poll",
)
def calc_dir_size(self): def calc_dir_size(self):
server_dt = HelperServers.get_server_data_by_id(self.server_id) server_dt = HelperServers.get_server_data_by_id(self.server_id)
@ -1502,6 +1553,7 @@ class ServerInstance:
"created": datetime.datetime.now().strftime( "created": datetime.datetime.now().strftime(
"%Y/%m/%d, %H:%M:%S" "%Y/%m/%d, %H:%M:%S"
), ),
"players_cache": self.player_cache,
}, },
) )
total_players += int(raw_ping_result.get("online")) total_players += int(raw_ping_result.get("online"))

View File

@ -421,6 +421,7 @@ class TasksManager:
) )
for item in jobs: for item in jobs:
logger.info(f"JOB: {item}") logger.info(f"JOB: {item}")
return task.schedule_id
def remove_all_server_tasks(self, server_id): def remove_all_server_tasks(self, server_id):
schedules = HelpersManagement.get_schedules_by_server(server_id) schedules = HelpersManagement.get_schedules_by_server(server_id)
@ -450,7 +451,6 @@ class TasksManager:
# created task a child of itself. # created task a child of itself.
if str(job_data.get("parent")) == str(sch_id): if str(job_data.get("parent")) == str(sch_id):
job_data["parent"] = None job_data["parent"] = None
HelpersManagement.update_scheduled_task(sch_id, job_data) HelpersManagement.update_scheduled_task(sch_id, job_data)
if not ( if not (
@ -775,31 +775,37 @@ class TasksManager:
def check_for_old_logs(self): def check_for_old_logs(self):
# check for server logs first # check for server logs first
self.controller.servers.check_for_old_logs() self.controller.servers.check_for_old_logs()
# check for crafty logs now try:
logs_path = os.path.join(self.controller.project_root, "logs") # check for crafty logs now
logs_delete_after = int( logs_path = os.path.join(self.controller.project_root, "logs")
self.helper.get_setting("crafty_logs_delete_after_days") logs_delete_after = int(
) self.helper.get_setting("crafty_logs_delete_after_days")
latest_log_files = [ )
"session.log", latest_log_files = [
"schedule.log", "session.log",
"tornado-access.log", "schedule.log",
"session.log", "tornado-access.log",
"commander.log", "session.log",
] "commander.log",
# we won't delete if delete logs after is set to 0 ]
if logs_delete_after != 0: # we won't delete if delete logs after is set to 0
log_files = list( if logs_delete_after != 0:
filter( log_files = list(
lambda val: val not in latest_log_files, filter(
os.listdir(logs_path), lambda val: val not in latest_log_files,
) os.listdir(logs_path),
)
)
for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file)
if Helpers.check_file_exists(
log_file_path
) and Helpers.is_file_older_than_x_days(
log_file_path, logs_delete_after
):
os.remove(log_file_path)
except:
logger.debug(
"Unable to find project root."
" If this issue persists please contact support."
) )
for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file)
if Helpers.check_file_exists(
log_file_path
) and Helpers.is_file_older_than_x_days(
log_file_path, logs_delete_after
):
os.remove(log_file_path)

View File

@ -289,9 +289,9 @@ class AjaxHandler(BaseHandler):
logger.warning("Server ID not found in send_command ajax call") logger.warning("Server ID not found in send_command ajax call")
Console.warning("Server ID not found in send_command ajax call") Console.warning("Server ID not found in send_command ajax call")
srv_obj = self.controller.servers.get_server_instance_by_id(server_id) svr_obj = self.controller.servers.get_server_instance_by_id(server_id)
if command == srv_obj.settings["stop_command"]: if command == svr_obj.settings["stop_command"]:
logger.info( logger.info(
"Stop command detected as terminal input - intercepting." "Stop command detected as terminal input - intercepting."
+ f"Starting Crafty's stop process for server with id: {server_id}" + f"Starting Crafty's stop process for server with id: {server_id}"
@ -313,8 +313,8 @@ class AjaxHandler(BaseHandler):
) )
command = None command = None
if command: if command:
if srv_obj.check_running(): if svr_obj.check_running():
srv_obj.send_command(command) svr_obj.send_command(command)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -382,23 +382,6 @@ class AjaxHandler(BaseHandler):
self.controller.cached_login = "login_1.jpg" self.controller.cached_login = "login_1.jpg"
return return
elif page == "kill":
if not permissions["Commands"] in user_perms:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Commands")
return
server_id = self.get_argument("id", None)
svr = self.controller.servers.get_server_instance_by_id(server_id)
try:
svr.kill()
time.sleep(5)
svr.cleanup_server_object()
svr.record_server_stats()
except Exception as e:
logger.error(
f"Could not find PID for requested termsig. Full error: {e}"
)
return
elif page == "eula": elif page == "eula":
server_id = self.get_argument("id", None) server_id = self.get_argument("id", None)
svr = self.controller.servers.get_server_instance_by_id(server_id) svr = self.controller.servers.get_server_instance_by_id(server_id)
@ -445,6 +428,21 @@ class AjaxHandler(BaseHandler):
new_server_id new_server_id
) )
new_server_obj.execution_command = server_data["execution_command"] new_server_obj.execution_command = server_data["execution_command"]
# reset executable path
if svr_obj.path in svr_obj.executable:
new_server_obj.executable = str(svr_obj.executable).replace(
svr_obj.path, new_server_obj.path
)
# reset run command path
if svr_obj.path in svr_obj.execution_command:
new_server_obj.execution_command = str(
svr_obj.execution_command
).replace(svr_obj.path, new_server_obj.path)
# reset log path
if svr_obj.path in svr_obj.log_path:
new_server_obj.log_path = str(svr_obj.log_path).replace(
svr_obj.path, new_server_obj.path
)
self.controller.servers.update_server(new_server_obj) self.controller.servers.update_server(new_server_obj)
# preserve backup config # preserve backup config
@ -505,6 +503,21 @@ class AjaxHandler(BaseHandler):
new_server_id new_server_id
) )
new_server_obj.execution_command = server_data["execution_command"] new_server_obj.execution_command = server_data["execution_command"]
# reset executable path
if server_obj.path in server_obj.executable:
new_server_obj.executable = str(server_obj.executable).replace(
server_obj.path, new_server_obj.path
)
# reset run command path
if server_obj.path in server_obj.execution_command:
new_server_obj.execution_command = str(
server_obj.execution_command
).replace(server_obj.path, new_server_obj.path)
# reset log path
if server_obj.path in server_obj.log_path:
new_server_obj.log_path = str(server_obj.log_path).replace(
server_obj.path, new_server_obj.path
)
self.controller.servers.update_server(new_server_obj) self.controller.servers.update_server(new_server_obj)
# preserve backup config # preserve backup config
@ -624,12 +637,6 @@ class AjaxHandler(BaseHandler):
user_perms = self.controller.server_perms.get_user_id_permissions_list( user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id exec_user["user_id"], server_id
) )
if page == "del_task":
if not permissions["Schedule"] in user_perms:
self.redirect("/panel/error?error=Unauthorized access to Tasks")
else:
sch_id = self.get_argument("schedule_id", "-404")
self.tasks_manager.remove_job(sch_id)
if page == "del_backup": if page == "del_backup":
if not permissions["Backup"] in user_perms: if not permissions["Backup"] in user_perms:
@ -649,12 +656,12 @@ class AjaxHandler(BaseHandler):
server_info = self.controller.servers.get_server_data_by_id(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id)
if not ( if not (
Helpers.in_path( self.helper.is_subdir(
Helpers.get_os_understandable_path(server_info["path"]), file_path file_path, Helpers.get_os_understandable_path(server_info["path"])
) )
or Helpers.in_path( or self.helper.is_subdir(
Helpers.get_os_understandable_path(server_info["backup_path"]),
file_path, file_path,
Helpers.get_os_understandable_path(server_info["backup_path"]),
) )
) or not Helpers.check_file_exists(os.path.abspath(file_path)): ) or not Helpers.check_file_exists(os.path.abspath(file_path)):
logger.warning(f"Invalid path in del_backup ajax call ({file_path})") logger.warning(f"Invalid path in del_backup ajax call ({file_path})")
@ -668,84 +675,6 @@ class AjaxHandler(BaseHandler):
): ):
os.remove(file_path) os.remove(file_path)
elif page == "delete_server":
if not permissions["Config"] in user_perms:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument("id", None)
logger.info(
f"Removing server from panel for server: "
f"{self.controller.servers.get_server_friendly_name(server_id)}"
)
server_data = self.controller.servers.get_server_data(server_id)
server_name = server_data["server_name"]
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"Deleted server {server_id} named {server_name}",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, False)
elif page == "delete_server_files":
if not permissions["Config"] in user_perms:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument("id", None)
logger.info(
f"Removing server and all associated files for server: "
f"{self.controller.servers.get_server_friendly_name(server_id)}"
)
server_data = self.controller.servers.get_server_data(server_id)
server_name = server_data["server_name"]
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"Deleted server {server_id} named {server_name}",
server_id,
self.get_remote_ip(),
)
for server in self.controller.servers.failed_servers:
if server["server_id"] == int(server_id):
return
self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, True)
elif page == "delete_unloaded_server":
if not permissions["Config"] in user_perms:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument("id", None)
logger.info(
f"Removing server and all associated files for server: "
f"{self.controller.servers.get_server_friendly_name(server_id)}"
)
server_data = self.controller.servers.get_server_data_by_id(server_id)
server_name = server_data["server_name"]
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"Deleted server {server_id} named {server_name}",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.remove_all_server_tasks(server_id)
for item in self.controller.servers.failed_servers[:]:
if item["server_id"] == int(server_id):
self.controller.servers.failed_servers.remove(item)
self.controller.remove_unloaded_server(server_id)
def check_server_id(self, server_id, page_name): def check_server_id(self, server_id, page_name):
if server_id is None: if server_id is None:
logger.warning( logger.warning(

View File

@ -57,11 +57,11 @@ class FileHandler(BaseHandler):
return return
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if not Helpers.in_path( if not self.helper.is_subdir(
file_path,
Helpers.get_os_understandable_path( Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"] self.controller.servers.get_server_data_by_id(server_id)["path"]
), ),
file_path,
) or not Helpers.check_file_exists(os.path.abspath(file_path)): ) or not Helpers.check_file_exists(os.path.abspath(file_path)):
logger.warning( logger.warning(
f"Invalid path in get_file file file ajax call ({file_path})" f"Invalid path in get_file file file ajax call ({file_path})"
@ -163,11 +163,11 @@ class FileHandler(BaseHandler):
return return
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if not Helpers.in_path( if not self.helper.is_subdir(
file_path,
Helpers.get_os_understandable_path( Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"] self.controller.servers.get_server_data_by_id(server_id)["path"]
), ),
file_path,
) or Helpers.check_file_exists(os.path.abspath(file_path)): ) or Helpers.check_file_exists(os.path.abspath(file_path)):
logger.warning( logger.warning(
f"Invalid path in create_file file ajax call ({file_path})" f"Invalid path in create_file file ajax call ({file_path})"
@ -196,11 +196,11 @@ class FileHandler(BaseHandler):
return return
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if not Helpers.in_path( if not self.helper.is_subdir(
dir_path,
Helpers.get_os_understandable_path( Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"] self.controller.servers.get_server_data_by_id(server_id)["path"]
), ),
dir_path,
) or Helpers.check_path_exists(os.path.abspath(dir_path)): ) or Helpers.check_path_exists(os.path.abspath(dir_path)):
logger.warning( logger.warning(
f"Invalid path in create_dir file ajax call ({dir_path})" f"Invalid path in create_dir file ajax call ({dir_path})"
@ -263,12 +263,12 @@ class FileHandler(BaseHandler):
server_info = self.controller.servers.get_server_data_by_id(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id)
if not ( if not (
Helpers.in_path( self.helper.is_subdir(
Helpers.get_os_understandable_path(server_info["path"]), file_path file_path, Helpers.get_os_understandable_path(server_info["path"])
) )
or Helpers.in_path( or self.helper.is_subdir(
Helpers.get_os_understandable_path(server_info["backup_path"]),
file_path, file_path,
Helpers.get_os_understandable_path(server_info["backup_path"]),
) )
) or not Helpers.check_file_exists(os.path.abspath(file_path)): ) or not Helpers.check_file_exists(os.path.abspath(file_path)):
logger.warning(f"Invalid path in del_file file ajax call ({file_path})") logger.warning(f"Invalid path in del_file file ajax call ({file_path})")
@ -296,8 +296,8 @@ class FileHandler(BaseHandler):
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
server_info = self.controller.servers.get_server_data_by_id(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id)
if not Helpers.in_path( if not self.helper.is_subdir(
Helpers.get_os_understandable_path(server_info["path"]), dir_path dir_path, Helpers.get_os_understandable_path(server_info["path"])
) or not Helpers.check_path_exists(os.path.abspath(dir_path)): ) or not Helpers.check_path_exists(os.path.abspath(dir_path)):
logger.warning(f"Invalid path in del_file file ajax call ({dir_path})") logger.warning(f"Invalid path in del_file file ajax call ({dir_path})")
Console.warning(f"Invalid path in del_file file ajax call ({dir_path})") Console.warning(f"Invalid path in del_file file ajax call ({dir_path})")
@ -348,11 +348,11 @@ class FileHandler(BaseHandler):
return return
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if not Helpers.in_path( if not self.helper.is_subdir(
file_path,
Helpers.get_os_understandable_path( Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"] self.controller.servers.get_server_data_by_id(server_id)["path"]
), ),
file_path,
) or not Helpers.check_file_exists(os.path.abspath(file_path)): ) or not Helpers.check_file_exists(os.path.abspath(file_path)):
logger.warning( logger.warning(
f"Invalid path in save_file file ajax call ({file_path})" f"Invalid path in save_file file ajax call ({file_path})"
@ -366,60 +366,6 @@ class FileHandler(BaseHandler):
with open(file_path, "w", encoding="utf-8") as file_object: with open(file_path, "w", encoding="utf-8") as file_object:
file_object.write(file_contents) file_object.write(file_contents)
elif page == "rename_file":
if not permissions["Files"] in user_perms:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
item_path = Helpers.get_os_understandable_path(
self.get_body_argument("item_path", default=None, strip=True)
)
new_item_name = self.get_body_argument(
"new_item_name", default=None, strip=True
)
if not self.check_server_id(server_id, "rename_file"):
return
server_id = bleach.clean(server_id)
if item_path is None or new_item_name is None:
logger.warning("Invalid path(s) in rename_file file ajax call")
Console.warning("Invalid path(s) in rename_file file ajax call")
return
if not Helpers.in_path(
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
item_path,
) or not Helpers.check_path_exists(os.path.abspath(item_path)):
logger.warning(
f"Invalid old name path in rename_file file ajax call ({server_id})"
)
Console.warning(
f"Invalid old name path in rename_file file ajax call ({server_id})"
)
return
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
new_item_path,
) or Helpers.check_path_exists(os.path.abspath(new_item_path)):
logger.warning(
f"Invalid new name path in rename_file file ajax call ({server_id})"
)
Console.warning(
f"Invalid new name path in rename_file file ajax call ({server_id})"
)
return
# RENAME
os.rename(item_path, new_item_path)
@tornado.web.authenticated @tornado.web.authenticated
def patch(self, page): def patch(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
@ -462,11 +408,11 @@ class FileHandler(BaseHandler):
Console.warning("Invalid path(s) in rename_file file ajax call") Console.warning("Invalid path(s) in rename_file file ajax call")
return return
if not Helpers.in_path( if not self.helper.is_subdir(
item_path,
Helpers.get_os_understandable_path( Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"] self.controller.servers.get_server_data_by_id(server_id)["path"]
), ),
item_path,
) or not Helpers.check_path_exists(os.path.abspath(item_path)): ) or not Helpers.check_path_exists(os.path.abspath(item_path)):
logger.warning( logger.warning(
f"Invalid old name path in rename_file file ajax call ({server_id})" f"Invalid old name path in rename_file file ajax call ({server_id})"
@ -478,11 +424,11 @@ class FileHandler(BaseHandler):
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name) new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
if not Helpers.in_path( if not self.helper.is_subdir(
new_item_path,
Helpers.get_os_understandable_path( Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"] self.controller.servers.get_server_data_by_id(server_id)["path"]
), ),
new_item_path,
) or Helpers.check_path_exists(os.path.abspath(new_item_path)): ) or Helpers.check_path_exists(os.path.abspath(new_item_path)):
logger.warning( logger.warning(
f"Invalid new name path in rename_file file ajax call ({server_id})" f"Invalid new name path in rename_file file ajax call ({server_id})"

View File

@ -6,7 +6,6 @@ import typing as t
import json import json
import logging import logging
import threading import threading
import shlex
import urllib.parse import urllib.parse
import bleach import bleach
import requests import requests
@ -17,7 +16,6 @@ from tornado import iostream
# TZLocal is set as a hidden import on win pipeline # TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone from tzlocal import get_localzone
from tzlocal.utils import ZoneInfoNotFoundError from tzlocal.utils import ZoneInfoNotFoundError
from croniter import croniter
from app.classes.models.servers import Servers from app.classes.models.servers import Servers
from app.classes.models.server_permissions import EnumPermissionsServer from app.classes.models.server_permissions import EnumPermissionsServer
@ -256,7 +254,12 @@ class PanelHandler(BaseHandler):
user_order = user_order["server_order"].split(",") user_order = user_order["server_order"].split(",")
page_servers = [] page_servers = []
server_ids = [] server_ids = []
for server in defined_servers:
server_ids.append(str(server.server_id))
if str(server.server_id) not in user_order:
# a little unorthodox, but this will cut out a loop.
# adding servers to the user order that don't already exist there.
user_order.append(str(server.server_id))
for server_id in user_order[:]: for server_id in user_order[:]:
for server in defined_servers[:]: for server in defined_servers[:]:
if str(server.server_id) == str(server_id): if str(server.server_id) == str(server_id):
@ -265,14 +268,7 @@ class PanelHandler(BaseHandler):
) )
user_order.remove(server_id) user_order.remove(server_id)
defined_servers.remove(server) defined_servers.remove(server)
break
for server in defined_servers:
server_ids.append(str(server.server_id))
if server not in page_servers:
page_servers.append(
DatabaseShortcuts.get_data_obj(server.server_object)
)
for server_id in user_order[:]: for server_id in user_order[:]:
# remove IDs in list that user no longer has access to # remove IDs in list that user no longer has access to
if str(server_id) not in server_ids: if str(server_id) not in server_ids:
@ -452,6 +448,7 @@ class PanelHandler(BaseHandler):
page_servers.append(server) page_servers.append(server)
un_used_servers.remove(server) un_used_servers.remove(server)
user_order.remove(server_id) user_order.remove(server_id)
break
# we only want to set these server stats values once. # we only want to set these server stats values once.
# We need to update the flag so it only hits that if once. # We need to update the flag so it only hits that if once.
flag += 1 flag += 1
@ -780,7 +777,21 @@ class PanelHandler(BaseHandler):
): ):
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access") self.redirect("/panel/error?error=Unauthorized access")
page_data["banned_players"] = get_banned_players_html() page_data["banned_players_html"] = get_banned_players_html()
page_data[
"banned_players"
] = self.controller.servers.get_banned_players(server_id)
server_instance = self.controller.servers.get_server_instance_by_id(
server_id
)
page_data["cached_players"] = server_instance.player_cache
for player in page_data["banned_players"]:
player["banned"] = True
temp_date = datetime.datetime.strptime(
player["created"], "%Y-%m-%d %H:%M:%S %z"
)
player["banned_on"] = (temp_date).strftime("%Y/%m/%d %H:%M:%S")
template = f"panel/server_{subpage}.html" template = f"panel/server_{subpage}.html"
@ -797,9 +808,9 @@ class PanelHandler(BaseHandler):
Helpers.get_os_understandable_path(server_info["backup_path"]), file Helpers.get_os_understandable_path(server_info["backup_path"]), file
) )
) )
if not Helpers.in_path( if not self.helper.is_subdir(
Helpers.get_os_understandable_path(server_info["backup_path"]),
backup_file, backup_file,
Helpers.get_os_understandable_path(server_info["backup_path"]),
) or not os.path.isfile(backup_file): ) or not os.path.isfile(backup_file):
self.redirect("/panel/error?error=Invalid path detected") self.redirect("/panel/error?error=Invalid path detected")
return return
@ -1053,7 +1064,7 @@ class PanelHandler(BaseHandler):
page_data["schedule"]["cron_string"] = "" page_data["schedule"]["cron_string"] = ""
page_data["schedule"]["delay"] = 0 page_data["schedule"]["delay"] = 0
page_data["schedule"]["time"] = "" page_data["schedule"]["time"] = ""
page_data["schedule"]["interval"] = "" page_data["schedule"]["interval"] = 1
# we don't need to check difficulty here. # we don't need to check difficulty here.
# We'll just default to basic for new schedules # We'll just default to basic for new schedules
page_data["schedule"]["difficulty"] = "basic" page_data["schedule"]["difficulty"] = "basic"
@ -1452,8 +1463,9 @@ class PanelHandler(BaseHandler):
server_info = self.controller.servers.get_server_data_by_id(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id)
if not Helpers.in_path( if not self.helper.is_subdir(
Helpers.get_os_understandable_path(server_info["path"]), file file,
Helpers.get_os_understandable_path(server_info["path"]),
) or not os.path.isfile(file): ) or not os.path.isfile(file):
self.redirect("/panel/error?error=Invalid path detected") self.redirect("/panel/error?error=Invalid path detected")
return return
@ -1556,156 +1568,6 @@ class PanelHandler(BaseHandler):
role = self.controller.roles.get_role(r) role = self.controller.roles.get_role(r)
exec_user_role.add(role["role_name"]) exec_user_role.add(role["role_name"])
if page == "server_detail":
if not permissions[
"Config"
] in self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
):
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_name = self.get_argument("server_name", None)
server_obj = self.controller.servers.get_server_obj(server_id)
shutdown_timeout = self.get_argument("shutdown_timeout", 60)
if superuser:
log_path = self.get_argument("log_path", "")
if log_path:
if Helpers.is_os_windows():
log_path.replace(" ", "^ ")
log_path = Helpers.wtol_path(log_path)
if not self.helper.validate_traversal(server_obj.path, log_path):
log_path = ""
executable = self.get_argument("executable", None)
execution_command = self.get_argument("execution_command", None)
server_ip = self.get_argument("server_ip", None)
server_port = self.get_argument("server_port", None)
if int(server_port) < 1 or int(server_port) > 65535:
self.redirect(
"/panel/error?error=Constraint Error: "
"Port must be greater than 0 and less than 65535"
)
return
executable_update_url = self.get_argument("executable_update_url", "")
show_status = int(float(self.get_argument("show_status", "0")))
else:
execution_command = server_obj.execution_command
executable = server_obj.executable
stop_command = self.get_argument("stop_command", None)
auto_start_delay = self.get_argument("auto_start_delay", "10")
auto_start = int(float(self.get_argument("auto_start", "0")))
crash_detection = int(float(self.get_argument("crash_detection", "0")))
logs_delete_after = int(float(self.get_argument("logs_delete_after", "0")))
java_selection = self.get_argument("java_selection", None)
# make sure there is no whitespace
ignored_exits = self.get_argument("ignored_exits", "").replace(" ", "")
# subpage = self.get_argument('subpage', None)
server_id = self.check_server_id()
if server_id is None:
return
if java_selection:
try:
if self.helper.is_os_windows():
execution_list = shlex.split(execution_command, posix=False)
else:
execution_list = shlex.split(execution_command, posix=True)
except ValueError:
self.redirect(
"/panel/error?error=Invalid execution command. Java path"
" must be surrounded by quotes."
" (Are you missing a closing quote?)"
)
if (
not any(
java_selection in path for path in Helpers.find_java_installs()
)
and java_selection != "java"
):
self.redirect(
"/panel/error?error=Attack attempted."
+ " A copy of this report is being sent to server owner."
)
self.controller.management.add_to_audit_log_raw(
exec_user["username"],
exec_user["user_id"],
server_id,
f"Attempted to send bad java path for {server_id}."
+ " Possible attack. Act accordingly.",
self.get_remote_ip(),
)
return
if java_selection != "java":
if self.helper.is_os_windows():
execution_list[0] = '"' + java_selection + '/bin/java"'
else:
execution_list[0] = '"' + java_selection + '"'
else:
execution_list[0] = "java"
execution_command = ""
for item in execution_list:
execution_command += item + " "
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
stale_executable = server_obj.executable
# Compares old jar name to page data being passed.
# If they are different we replace the executable name in the
if str(stale_executable) != str(executable):
execution_command = execution_command.replace(
str(stale_executable), str(executable)
)
server_obj.server_name = server_name
server_obj.shutdown_timeout = shutdown_timeout
if superuser:
if Helpers.validate_traversal(
self.helper.get_servers_root_dir(), server_obj.path
):
server_obj.log_path = log_path
if Helpers.validate_traversal(
self.helper.get_servers_root_dir(), executable
):
server_obj.executable = executable
server_obj.execution_command = execution_command
server_obj.server_ip = server_ip
server_obj.server_port = server_port
server_obj.executable_update_url = executable_update_url
server_obj.show_status = show_status
else:
server_obj.log_path = server_obj.log_path
server_obj.executable = server_obj.executable
server_obj.execution_command = execution_command
server_obj.server_ip = server_obj.server_ip
server_obj.server_port = server_obj.server_port
server_obj.executable_update_url = server_obj.executable_update_url
server_obj.stop_command = stop_command
server_obj.auto_start_delay = auto_start_delay
server_obj.auto_start = auto_start
server_obj.crash_detection = crash_detection
server_obj.logs_delete_after = logs_delete_after
server_obj.ignored_exits = ignored_exits
failed = False
for servers in self.controller.servers.failed_servers:
if servers["server_id"] == int(server_id):
failed = True
if not failed:
self.controller.servers.update_server(server_obj)
else:
self.controller.servers.update_unloaded_server(server_obj)
self.controller.servers.init_all_servers()
self.controller.servers.crash_detection(server_obj)
self.controller.servers.refresh_server_settings(server_id)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"Edited server {server_id} named {server_name}",
server_id,
self.get_remote_ip(),
)
self.redirect(f"/panel/server_detail?id={server_id}&subpage=config")
if page == "server_backup": if page == "server_backup":
logger.debug(self.request.arguments) logger.debug(self.request.arguments)
@ -1802,336 +1664,6 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/config_json") self.redirect("/panel/config_json")
if page == "new_schedule":
server_id = self.check_server_id()
if not server_id:
return
if (
not permissions["Schedule"]
in self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
and not superuser
):
self.redirect(
"/panel/error?error=Unauthorized access: User not authorized"
)
return
difficulty = bleach.clean(self.get_argument("difficulty", None))
server_obj = self.controller.servers.get_server_obj(server_id)
enabled = bleach.clean(self.get_argument("enabled", "0"))
name = bleach.clean(self.get_argument("name", ""))
if difficulty == "basic":
action = bleach.clean(self.get_argument("action", None))
interval = bleach.clean(self.get_argument("interval", None))
interval_type = bleach.clean(self.get_argument("interval_type", None))
# only check for time if it's number of days
if interval_type == "days":
sch_time = bleach.clean(self.get_argument("time", None))
if int(interval) > 30:
self.redirect(
"/panel/error?error=Invalid argument."
" Days must be 30 or fewer."
)
return
if action == "command":
command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
elif difficulty == "reaction":
interval_type = "reaction"
action = bleach.clean(self.get_argument("action", None))
delay = bleach.clean(self.get_argument("delay", None))
parent = bleach.clean(self.get_argument("parent", None))
if action == "command":
command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
else:
interval_type = ""
cron_string = bleach.clean(self.get_argument("cron", ""))
if not croniter.is_valid(cron_string):
self.redirect(
"/panel/error?error=INVALID FORMAT: Invalid Cron Format."
)
return
action = bleach.clean(self.get_argument("action", None))
if action == "command":
command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
if bleach.clean(self.get_argument("enabled", "0")) == "1":
enabled = True
else:
enabled = False
if bleach.clean(self.get_argument("one_time", "0")) == "1":
one_time = True
else:
one_time = False
if interval_type == "days":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"start_time": sch_time,
"enabled": enabled,
"one_time": one_time,
"cron_string": "",
"parent": None,
"delay": 0,
}
elif difficulty == "reaction":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": "",
# We'll base every interval off of a midnight start time.
"start_time": "",
"command": command,
"cron_string": "",
"enabled": enabled,
"one_time": one_time,
"parent": parent,
"delay": delay,
}
elif difficulty == "advanced":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": "",
"interval": "",
# We'll base every interval off of a midnight start time.
"start_time": "",
"command": command,
"cron_string": cron_string,
"enabled": enabled,
"one_time": one_time,
"parent": None,
"delay": 0,
}
else:
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"enabled": enabled,
# We'll base every interval off of a midnight start time.
"start_time": "00:00",
"one_time": one_time,
"cron_string": "",
"parent": None,
"delay": 0,
}
self.tasks_manager.schedule_job(job_data)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"Edited server {server_id}: added scheduled job",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.reload_schedule_from_db()
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
if page == "edit_schedule":
server_id = self.check_server_id()
if not server_id:
return
if (
not permissions["Schedule"]
in self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
and not superuser
):
self.redirect(
"/panel/error?error=Unauthorized access: User not authorized"
)
return
sch_id = self.get_argument("sch_id", None)
if sch_id is None:
self.redirect("/panel/error?error=Invalid Schedule ID")
difficulty = bleach.clean(self.get_argument("difficulty", None))
server_obj = self.controller.servers.get_server_obj(server_id)
enabled = bleach.clean(self.get_argument("enabled", "0"))
name = bleach.clean(self.get_argument("name", ""))
if difficulty == "basic":
action = bleach.clean(self.get_argument("action", None))
interval = bleach.clean(self.get_argument("interval", None))
interval_type = bleach.clean(self.get_argument("interval_type", None))
# only check for time if it's number of days
if interval_type == "days":
sch_time = bleach.clean(self.get_argument("time", None))
if int(interval) > 30:
self.redirect(
"/panel/error?error=Invalid argument."
" Days must be 30 or fewer."
)
return
if action == "command":
command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
elif difficulty == "reaction":
interval_type = "reaction"
action = bleach.clean(self.get_argument("action", None))
delay = bleach.clean(self.get_argument("delay", None))
parent = bleach.clean(self.get_argument("parent", None))
if action == "command":
command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
parent = bleach.clean(self.get_argument("parent", None))
else:
interval_type = ""
cron_string = bleach.clean(self.get_argument("cron", ""))
if not croniter.is_valid(cron_string):
self.redirect(
"/panel/error?error=INVALID FORMAT: Invalid Cron Format."
)
return
action = bleach.clean(self.get_argument("action", None))
if action == "command":
command = self.get_argument("command", None)
elif action == "start":
command = "start_server"
elif action == "stop":
command = "stop_server"
elif action == "restart":
command = "restart_server"
elif action == "backup":
command = "backup_server"
if bleach.clean(self.get_argument("enabled", "0")) == "1":
enabled = True
else:
enabled = False
if bleach.clean(self.get_argument("one_time", "0")) == "1":
one_time = True
else:
one_time = False
if interval_type == "days":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"start_time": sch_time,
"enabled": enabled,
"one_time": one_time,
"cron_string": "",
"parent": None,
"delay": 0,
}
elif difficulty == "advanced":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": "",
"interval": "",
# We'll base every interval off of a midnight start time.
"start_time": "",
"command": command,
"cron_string": cron_string,
"delay": "",
"parent": "",
"enabled": enabled,
"one_time": one_time,
}
elif difficulty == "reaction":
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": "",
# We'll base every interval off of a midnight start time.
"start_time": "",
"command": command,
"cron_string": "",
"enabled": enabled,
"one_time": one_time,
"parent": parent,
"delay": delay,
}
else:
job_data = {
"name": name,
"server_id": server_id,
"action": action,
"interval_type": interval_type,
"interval": interval,
"command": command,
"enabled": enabled,
# We'll base every interval off of a midnight start time.
"start_time": "00:00",
"delay": "",
"parent": "",
"one_time": one_time,
"cron_string": "",
}
self.tasks_manager.update_job(sch_id, job_data)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"Edited server {server_id}: updated schedule",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.reload_schedule_from_db()
self.redirect(f"/panel/server_detail?id={server_id}&subpage=schedules")
elif page == "edit_user": elif page == "edit_user":
if bleach.clean(self.get_argument("username", None)).lower() == "system": if bleach.clean(self.get_argument("username", None)).lower() == "system":
self.redirect( self.redirect(

View File

@ -50,12 +50,15 @@ class PublicHandler(BaseHandler):
if page == "login": if page == "login":
template = "public/login.html" template = "public/login.html"
elif page == 404: elif page == "404":
template = "public/404.html" template = "public/404.html"
elif page == "error": elif page == "error":
template = "public/error.html" template = "public/error.html"
elif page == "offline":
template = "public/offline.html"
elif page == "logout": elif page == "logout":
self.clear_cookie("token") self.clear_cookie("token")
# self.clear_cookie("user") # self.clear_cookie("user")

View File

@ -4,6 +4,36 @@ from peewee import DoesNotExist
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
modify_role_schema = { modify_role_schema = {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
},
"servers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"server_id": {
"type": "integer",
"minimum": 1,
},
"permissions": {
"type": "string",
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
},
},
"required": ["server_id", "permissions"],
},
},
"manager": {"type": ["integer", "null"]},
},
"additionalProperties": False,
"minProperties": 1,
}
basic_modify_role_schema = {
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "name": {
@ -109,7 +139,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
) )
try: try:
validate(data, modify_role_schema) if auth_data[4]["superuser"]:
validate(data, modify_role_schema)
else:
validate(data, basic_modify_role_schema)
except ValidationError as e: except ValidationError as e:
return self.finish_json( return self.finish_json(
400, 400,

View File

@ -13,20 +13,39 @@ server_patch_schema = {
"type": "object", "type": "object",
"properties": { "properties": {
"server_name": {"type": "string", "minLength": 1}, "server_name": {"type": "string", "minLength": 1},
"path": {"type": "string", "minLength": 1},
"backup_path": {"type": "string"}, "backup_path": {"type": "string"},
"executable": {"type": "string"}, "executable": {"type": "string"},
"log_path": {"type": "string", "minLength": 1}, "log_path": {"type": "string", "minLength": 1},
"execution_command": {"type": "string", "minLength": 1}, "execution_command": {"type": "string", "minLength": 1},
"java_selection": {"type": "string"},
"auto_start": {"type": "boolean"}, "auto_start": {"type": "boolean"},
"auto_start_delay": {"type": "integer"}, "auto_start_delay": {"type": "integer", "minimum": 0},
"crash_detection": {"type": "boolean"}, "crash_detection": {"type": "boolean"},
"stop_command": {"type": "string"}, "stop_command": {"type": "string"},
"executable_update_url": {"type": "string", "minLength": 1}, "executable_update_url": {"type": "string"},
"server_ip": {"type": "string", "minLength": 1}, "server_ip": {"type": "string", "minLength": 1},
"server_port": {"type": "integer"}, "server_port": {"type": "integer"},
"logs_delete_after": {"type": "integer"}, "shutdown_timeout": {"type": "integer", "minimum": 0},
"type": {"type": "string", "minLength": 1}, "logs_delete_after": {"type": "integer", "minimum": 0},
"ignored_exits": {"type": "string"},
"show_status": {"type": "boolean"},
},
"additionalProperties": False,
"minProperties": 1,
}
basic_server_patch_schema = {
"type": "object",
"properties": {
"server_name": {"type": "string", "minLength": 1},
"executable": {"type": "string"},
"java_selection": {"type": "string"},
"auto_start": {"type": "boolean"},
"auto_start_delay": {"type": "integer", "minimum": 0},
"crash_detection": {"type": "boolean"},
"stop_command": {"type": "string"},
"shutdown_timeout": {"type": "integer"},
"logs_delete_after": {"type": "integer", "minimum": 0},
"ignored_exits": {"type": "string"},
}, },
"additionalProperties": False, "additionalProperties": False,
"minProperties": 1, "minProperties": 1,
@ -63,7 +82,11 @@ class ApiServersServerIndexHandler(BaseApiHandler):
) )
try: try:
validate(data, server_patch_schema) # prevent general users from becoming bad actors
if auth_data[4]["superuser"]:
validate(data, server_patch_schema)
else:
validate(data, basic_server_patch_schema)
except ValidationError as e: except ValidationError as e:
return self.finish_json( return self.finish_json(
400, 400,
@ -88,9 +111,24 @@ class ApiServersServerIndexHandler(BaseApiHandler):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
server_obj = self.controller.servers.get_server_obj(server_id) server_obj = self.controller.servers.get_server_obj(server_id)
java_flag = False
for key in data: for key in data:
# If we don't validate the input there could be security issues # If we don't validate the input there could be security issues
if key == "java_selection" and data[key] != "none":
try:
command = self.helper.get_execution_java(
data[key], server_obj.execution_command
)
setattr(server_obj, "execution_command", command)
except ValueError:
return self.finish_json(
400, {"status": "error", "error": "INVALID EXECUTION COMMAND"}
)
java_flag = True
if key != "path": if key != "path":
if key == "execution_command" and java_flag:
continue
setattr(server_obj, key, data[key]) setattr(server_obj, key, data[key])
self.controller.servers.update_server(server_obj) self.controller.servers.update_server(server_obj)
@ -134,7 +172,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
) )
self.tasks_manager.remove_all_server_tasks(server_id) self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, remove_files) failed = False
for item in self.controller.servers.failed_servers[:]:
if item["server_id"] == int(server_id):
self.controller.servers.failed_servers.remove(item)
failed = True
if failed:
self.controller.remove_unloaded_server(server_id)
else:
self.controller.remove_server(server_id, remove_files)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
auth_data[4]["user_id"], auth_data[4]["user_id"],

View File

@ -35,7 +35,13 @@ class ApiServersServerStdinHandler(BaseApiHandler):
"Please report this to the devs" "Please report this to the devs"
) )
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
decoded = self.request.body.decode("utf-8")
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"Sent command ({decoded}) to terminal",
server_id=0,
source_ip=self.get_remote_ip(),
)
if svr.send_command(self.request.body.decode("utf-8")): if svr.send_command(self.request.body.decode("utf-8")):
return self.finish_json( return self.finish_json(
200, 200,

View File

@ -1,16 +1,122 @@
# TODO: create and read # TODO: create and read
import json
import logging import logging
from croniter import croniter
from jsonschema import ValidationError, validate
from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
new_task_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"enabled": {
"type": "boolean",
"default": True,
},
"action": {
"type": "string",
},
"interval": {"type": "integer"},
"interval_type": {
"type": "string",
"enum": [
# Basic tasks
"hours",
"minutes",
"days",
# Chain reaction tasks:
"reaction",
# CRON tasks:
"",
],
},
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
"command": {"type": ["string", "null"]},
"one_time": {"type": "boolean", "default": False},
"cron_string": {"type": "string", "default": ""},
"parent": {"type": ["integer", "null"]},
"delay": {"type": "integer", "default": 0},
},
"additionalProperties": False,
"minProperties": 1,
}
class ApiServersServerTasksIndexHandler(BaseApiHandler): class ApiServersServerTasksIndexHandler(BaseApiHandler):
def get(self, server_id: str, task_id: str): def get(self, server_id: str, task_id: str):
pass pass
def post(self, server_id: str, task_id: str): def post(self, server_id: str):
pass auth_data = self.authenticate_user()
if not auth_data:
return
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, new_task_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.SCHEDULE
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
data["server_id"] = server_id
if not data.get("start_time"):
data["start_time"] = "00:00"
# validate cron string
if "cron_string" in data:
if data["cron_string"] != "" and not croniter.is_valid(data["cron_string"]):
return self.finish_json(
405,
{
"status": "error",
"error": self.helper.translation.translate(
"error",
"cronFormat",
self.controller.users.get_user_lang_by_id(
auth_data[4]["user_id"]
),
),
},
)
if "parent" not in data:
data["parent"] = None
task_id = self.tasks_manager.schedule_job(data)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"Edited server {server_id}: added schedule",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.reload_schedule_from_db()
self.finish_json(200, {"status": "ok", "data": {"schedule_id": task_id}})

View File

@ -3,6 +3,7 @@
import json import json
import logging import logging
from croniter import croniter
from jsonschema import ValidationError, validate from jsonschema import ValidationError, validate
from app.classes.models.server_permissions import EnumPermissionsServer from app.classes.models.server_permissions import EnumPermissionsServer
@ -35,6 +36,7 @@ task_patch_schema = {
"", "",
], ],
}, },
"name": {"type": "string"},
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"}, "start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
"command": {"type": ["string", "null"]}, "command": {"type": ["string", "null"]},
"one_time": {"type": "boolean", "default": False}, "one_time": {"type": "boolean", "default": False},
@ -49,10 +51,47 @@ task_patch_schema = {
class ApiServersServerTasksTaskIndexHandler(BaseApiHandler): class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
def get(self, server_id: str, task_id: str): def get(self, server_id: str, task_id: str):
pass auth_data = self.authenticate_user()
if not auth_data:
return
if (
EnumPermissionsServer.SCHEDULE
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(200, self.controller.management.get_scheduled_task(task_id))
def delete(self, server_id: str, task_id: str): def delete(self, server_id: str, task_id: str):
pass auth_data = self.authenticate_user()
if not auth_data:
return
if (
EnumPermissionsServer.SCHEDULE
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
self.tasks_manager.remove_job(task_id)
except Exception:
return self.finish_json(
400, {"status": "error", "error": "NO SCHEDULE FOUND"}
)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"Edited server {server_id}: removed schedule",
server_id,
self.get_remote_ip(),
)
self.tasks_manager.reload_schedule_from_db()
return self.finish_json(200, {"status": "ok"})
def patch(self, server_id: str, task_id: str): def patch(self, server_id: str, task_id: str):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
@ -96,6 +135,22 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
if str(data.get("parent")) == str(task_id) and data.get("parent") is not None: if str(data.get("parent")) == str(task_id) and data.get("parent") is not None:
data["parent"] = None data["parent"] = None
data["server_id"] = server_id
if "cron_string" in data:
if data["cron_string"] != "" and not croniter.is_valid(data["cron_string"]):
return self.finish_json(
405,
{
"status": "error",
"error": self.helper.translation.translate(
"error",
"cronFormat",
self.controller.users.get_user_lang_by_id(
auth_data[4]["user_id"]
),
),
},
)
self.tasks_manager.update_job(task_id, data) self.tasks_manager.update_job(task_id, data)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(

View File

@ -145,7 +145,11 @@ class ServerHandler(BaseHandler):
"not a server creator or server limit reached" "not a server creator or server limit reached"
) )
return return
page_data["server_api"] = False
if page_data["online"]:
page_data["server_api"] = self.helper.check_address_status(
"https://serverjars.com/api/fetchTypes"
)
page_data["server_types"] = self.controller.server_jars.get_serverjar_data() page_data["server_types"] = self.controller.server_jars.get_serverjar_data()
page_data["js_server_types"] = json.dumps( page_data["js_server_types"] = json.dumps(
self.controller.server_jars.get_serverjar_data() self.controller.server_jars.get_serverjar_data()
@ -164,7 +168,7 @@ class ServerHandler(BaseHandler):
"not a server creator or server limit reached" "not a server creator or server limit reached"
) )
return return
page_data["server_api"] = True
template = "server/bedrock_wizard.html" template = "server/bedrock_wizard.html"
if page == "steam_cmd_step1": if page == "steam_cmd_step1":
@ -350,7 +354,7 @@ class ServerHandler(BaseHandler):
if import_type == "import_jar": if import_type == "import_jar":
if self.helper.is_subdir( if self.helper.is_subdir(
import_server_path, self.controller.project_root self.controller.project_root, import_server_path
): ):
self.redirect( self.redirect(
"/panel/error?error=Loop Error: The selected path will cause" "/panel/error?error=Loop Error: The selected path will cause"
@ -516,7 +520,7 @@ class ServerHandler(BaseHandler):
if import_type == "import_jar": if import_type == "import_jar":
if self.helper.is_subdir( if self.helper.is_subdir(
import_server_path, self.controller.project_root self.controller.project_root, import_server_path
): ):
self.redirect( self.redirect(
"/panel/error?error=Loop Error: The selected path will cause" "/panel/error?error=Loop Error: The selected path will cause"

View File

@ -11,6 +11,9 @@ except ModuleNotFoundError as e:
class CustomStaticHandler(tornado.web.StaticFileHandler): class CustomStaticHandler(tornado.web.StaticFileHandler):
def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
# This is for the mobile app service worker
if self.request.path.find("service-worker.js") != -1:
self.set_header("Service-Worker-Allowed", "/")
try: try:
return super().validate_absolute_path(root, absolute_path) return super().validate_absolute_path(root, absolute_path)
except tornado.web.HTTPError as error: except tornado.web.HTTPError as error:

View File

@ -278,11 +278,11 @@ class UploadHandler(BaseHandler):
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( if not self.helper.is_subdir(
full_path,
Helpers.get_os_understandable_path( Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"] self.controller.servers.get_server_data_by_id(server_id)["path"]
), ),
full_path,
): ):
logger.warning( logger.warning(
f"User {user_id} tried to upload a file to {server_id} " f"User {user_id} tried to upload a file to {server_id} "

View File

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

View File

@ -0,0 +1,40 @@
{
"background_color": "#222436",
"description": "Crafty Controller is a free and open-source Minecraft launcher and manager that allows users to start and administer Minecraft servers from a user-friendly interface.",
"dir": "ltr",
"display": "standalone",
"name": "Crafty Controller",
"orientation": "any",
"scope": "/",
"short_name": "Crafty",
"start_url": "/",
"theme_color": "#222436",
"categories": ["utilities"],
"icons": [
{
"src": "/static/assets/images/Crafty_4-0_Logo_square.ico",
"type": "image/x-icon",
"sizes":"128x128"
},
{
"src": "/static/assets/images/Crafty_4-0.png",
"type": "image/png",
"sizes": "144x144",
"purpose": "any"
},
{
"src": "/static/assets/images/crafty-logo-square-1024.png",
"type": "image/png",
"sizes": "1024x1024",
"purpose": "any"
},
{
"src": "/static/assets/images/crafty-logo-square-96.png",
"type": "image/png",
"sizes": "96x96",
"purpose": "any"
}
],
"lang": "en",
"prefer_related_applications": false
}

View File

@ -134,4 +134,59 @@ body {
.accordion .card { .accordion .card {
margin-bottom: 0px; margin-bottom: 0px;
} }
.bootbox-body {
text-align: center;
}
/**************************************************************/
/* CSS for Froms Displays */
/**************************************************************/
div>.input-group>.custom-file-input {
position: relative !important;
-webkit-box-flex: 1 !important;
-ms-flex: 1 1 auto !important;
flex: 1 1 auto !important;
width: 1% !important;
margin-bottom: 0 !important;
border: 1px solid var(--outline);
}
div>.input-group>.form-control-file {
position: relative !important;
-webkit-box-flex: 1 !important;
-ms-flex: 1 1 auto !important;
flex: 1 1 auto !important;
width: 1% !important;
margin-bottom: 0 !important;
border: 1px solid var(--outline);
}
.custom-picker {
border: 1px solid var(--outline);
}
div>.input-group>.form-control {
position: relative !important;
-webkit-box-flex: 1 !important;
-ms-flex: 1 1 auto !important;
flex: 1 1 auto !important;
width: 1% !important;
margin-bottom: 0 !important;
border: 1px solid var(--outline);
}
.input-group>.input-group-append>button.upload-button {
height: calc(1.5em + 0.75rem + 2px);
}
.no-scroll {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.no-scroll::-webkit-scrollbar {
display: none;
}
/**************************************************************/

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 683.6 143.8" style="enable-background:new 0 0 683.6 143.8;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.85;fill:#FFFFFF;enable-background:new ;}
.st1{opacity:0.85;}
.st2{fill:#FFFFFF;}
.st3{fill:none;}
.st4{fill:url(#SVGID_1_);}
.st5{fill:url(#SVGID_00000137122815686618769650000009047437546445953421_);}
.st6{fill:url(#SVGID_00000170963539203169094570000007184871682409824703_);}
.st7{fill:url(#SVGID_00000169549353698428389090000007910489870824235905_);}
.st8{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000029754379306852418700000008865188217784465572_);}
</style>
<path class="st0" d="M175.8,111.5h17.6v3.8h-13.2v8.9h12.1v3.7h-12.1v11.8h-4.4V111.5z"/>
<path class="st0" d="M196.3,119.1h4.2v3.5h0.1c0.4-2.3,2.4-3.9,4.7-3.9c0.5,0,1,0.1,1.5,0.2v3.9c-0.6-0.2-1.3-0.3-1.9-0.2
c-2.7,0-4.4,1.8-4.4,4.8v12.3h-4.2L196.3,119.1z"/>
<path class="st0" d="M207.2,129.4L207.2,129.4c0-6.6,3.9-10.6,9.7-10.6s9.8,4,9.8,10.6l0,0c0,6.6-3.9,10.7-9.8,10.7
S207.2,136,207.2,129.4z M222.4,129.4L222.4,129.4c0-4.5-2.2-7.1-5.5-7.1s-5.4,2.6-5.4,7.1l0,0c0,4.5,2.2,7.2,5.5,7.2
S222.4,133.9,222.4,129.4L222.4,129.4z"/>
<path class="st0" d="M229.6,119.1h4.2v3.2h0.1c1-2.3,3.2-3.7,5.7-3.6c2.6-0.2,5,1.5,5.7,4h0.1c1.1-2.5,3.6-4.1,6.4-4
c4.1,0,6.7,2.7,6.7,6.8v14.1h-4.2v-13.1c0-2.7-1.4-4.2-3.9-4.2c-2.3,0-4.2,1.8-4.3,4.2c0,0.1,0,0.2,0,0.3v12.9H242v-13.4
c0.2-2-1.3-3.8-3.3-3.9c-0.2,0-0.4,0-0.5,0c-2.4,0-4.3,2-4.3,4.3c0,0.1,0,0.2,0,0.3v12.7h-4.2L229.6,119.1z"/>
<g id="Layer_2_00000138553854520646606810000012156271018779627156_" class="st1">
<g id="Layer_1-2">
<path class="st2" d="M343.7,139.9c-6.9,0-12.5-5.6-12.5-12.5s5.6-12.5,12.5-12.5c2.1,0,4.2,0.5,6,1.5c1.8,1,3.3,2.4,4.3,4.1
l-4.1,2.4c-0.6-1.1-1.5-1.9-2.5-2.5c-3.1-1.6-6.8-1.1-9.4,1.3c-1.5,1.5-2.2,3.6-2.1,5.7c-0.1,2.1,0.7,4.1,2.1,5.7
c1.5,1.5,3.5,2.3,5.7,2.2c1.3,0,2.6-0.3,3.7-0.9c1.1-0.6,2-1.4,2.5-2.5l4.1,2.4c-1,1.7-2.5,3.2-4.3,4.1
C347.8,139.4,345.8,139.9,343.7,139.9z"/>
<path class="st2" d="M361.4,122.3v3c0.3-1,1.1-1.9,2-2.5c1-0.6,2.1-0.9,3.2-0.8v4.9c-1.3-0.2-2.6,0.1-3.6,0.8
c-1.1,0.8-1.7,2.2-1.6,3.5v8.2H357v-17.2H361.4z"/>
<path class="st2" d="M381.6,124.3v-2h4.4v17.2h-4.4v-2c-1.4,1.7-3.4,2.6-5.6,2.5c-2.2,0-4.4-0.9-5.9-2.6c-1.6-1.8-2.5-4.1-2.4-6.5
c-0.1-2.4,0.8-4.7,2.4-6.4c1.5-1.7,3.6-2.7,5.9-2.7C378.1,121.7,380.2,122.6,381.6,124.3z M373.4,134.3c1.9,1.8,4.9,1.8,6.8,0
c0.9-0.9,1.4-2.2,1.4-3.5c0.1-1.3-0.4-2.6-1.4-3.5c-1.9-1.8-4.9-1.8-6.8,0c-0.9,0.9-1.4,2.2-1.3,3.5
C372,132.1,372.5,133.4,373.4,134.3z"/>
<path class="st2" d="M399.2,115v4.2c-2.4-0.2-3.6,0.8-3.7,2.9v0.2h3.6v4.3h-3.6v12.9h-4.4v-12.9h-2.5v-4.2h2.5v-0.2
c-0.2-2,0.6-4.1,2-5.5C394.5,115.3,396.6,114.8,399.2,115z"/>
<path class="st2" d="M411.6,122.3v4.2h-3.9v7.1c0,0.5,0.1,1,0.5,1.3c0.4,0.3,0.8,0.5,1.3,0.5c0.7,0,1.4,0,2.1,0v4
c-3,0.3-5.1,0.1-6.4-0.8s-1.9-2.5-1.9-4.9v-7.1h-3v-4.2h3v-3.5l4.4-1.3v4.8L411.6,122.3z"/>
<path class="st2" d="M427.2,124.3v-2h4.4v17.2h-4.4v-2c-1.4,1.7-3.4,2.6-5.6,2.5c-2.2,0-4.4-0.9-5.9-2.6c-1.6-1.8-2.5-4.1-2.4-6.5
c-0.1-2.4,0.8-4.7,2.4-6.4c1.5-1.7,3.6-2.7,5.9-2.7C423.8,121.7,425.9,122.6,427.2,124.3z M419.1,134.3c1.9,1.8,4.9,1.8,6.8,0
c0.9-0.9,1.4-2.2,1.4-3.5c0-1.3-0.4-2.5-1.4-3.5c-1.9-1.8-4.9-1.8-6.8,0c-0.9,0.9-1.4,2.2-1.3,3.5
C417.7,132.1,418.2,133.4,419.1,134.3L419.1,134.3z"/>
<path class="st2" d="M440.1,122.3v3c0.4-1,1.1-1.9,2-2.5c1-0.6,2.1-0.9,3.2-0.8v4.9c-1.3-0.2-2.6,0.1-3.6,0.8
c-1.1,0.8-1.7,2.2-1.6,3.5v8.2h-4.4v-17.2H440.1z"/>
<path class="st2" d="M461.9,137.3c-3.6,3.6-9.3,3.6-12.9,0s-3.6-9.3,0-12.9l0,0c3.6-3.5,9.3-3.5,12.9,0.1c1.7,1.7,2.6,4,2.6,6.4
C464.5,133.3,463.6,135.6,461.9,137.3z M452.1,134.3c1.9,1.8,4.8,1.8,6.7,0c1.8-1.9,1.8-4.9,0-6.8c-1.9-1.8-4.8-1.8-6.7,0
C450.3,129.4,450.3,132.3,452.1,134.3L452.1,134.3z"/>
<path class="st2" d="M320,137.6l-2.9-20.3c-0.4-2.7-2.7-4.7-5.5-4.7h-9c-0.3,0-0.5,0.2-0.7,0.4l-0.9,2H292l-0.9-2
c-0.1-0.3-0.4-0.4-0.7-0.4h-9c-2.7,0-5.1,2-5.5,4.7l-2.9,20.3c-0.4,3,1.7,5.8,4.7,6.2c0,0,0,0,0,0l0,0c0.3,0,0.5,0.1,0.8,0.1h36
c3,0,5.5-2.5,5.5-5.5l0,0C320,138.1,320,137.8,320,137.6z M287.1,130c-2.7,0-4.9-2.2-4.9-4.9c0-2.7,2.2-4.9,4.9-4.9
c2.7,0,4.9,2.2,4.9,4.9c0,0,0,0,0,0l0,0C292,127.8,289.8,130,287.1,130z M296.5,138c-2.7,0-4.9-2.2-4.9-4.9h9.8
C301.4,135.8,299.3,138,296.5,138L296.5,138L296.5,138z M305.9,130c-2.7,0-4.9-2.2-4.9-4.9c0-2.7,2.2-4.9,4.9-4.9
c2.7,0,4.9,2.2,4.9,4.9c0,0,0,0,0,0l0,0C310.8,127.8,308.6,130,305.9,130L305.9,130z"/>
</g>
</g>
<path class="st2" d="M133.1,19.2H9.7c-1.8,0-3.2-1.4-3.2-3.2V3.2C6.5,1.5,7.9,0,9.7,0h123.4c1.8,0,3.2,1.4,3.2,3.2V16
C136.3,17.8,134.9,19.2,133.1,19.2"/>
<path class="st2" d="M23.6,36.7c-3.4,0-6.7,1.6-8.8,4.3c-2.9,3.6-4.1,8.3-3.2,12.8l9.2,51.9c1.2,6.6,6.2,11.4,12.1,11.4H110
c5.8,0,10.9-4.8,12.1-11.4l9.2-51.9c0.8-4.5-0.4-9.2-3.3-12.8c-2.1-2.7-5.4-4.3-8.8-4.3H23.6z M110,128.3H32.8
c-11.3,0-21-8.7-23.1-20.7L0.5,55.8c-1.5-7.8,0.6-15.9,5.7-22c4.3-5.2,10.7-8.3,17.4-8.3h95.6c6.8,0.1,13.1,3.1,17.4,8.3
c5.1,6.1,7.2,14.2,5.7,22l-9.2,51.9C130.9,119.7,121.2,128.4,110,128.3"/>
<path class="st2" d="M120.8,23.8v-2.2c2,0,3.5-1.6,3.5-3.6c0-1.8-1.5-3.4-3.3-3.5H21.6c-2,0.1-3.5,1.8-3.4,3.7
c0.1,1.8,1.5,3.3,3.4,3.4v2.2c-3.2-0.1-5.7-2.8-5.6-6c0.1-3,2.5-5.4,5.6-5.6h99.2c3.2-0.1,5.9,2.4,6,5.6s-2.4,5.9-5.6,6
C121.1,23.8,121,23.8,120.8,23.8"/>
<path class="st2" d="M120.8,33.1H21.6c-3.2,0-5.8-2.6-5.8-5.8c0-3.2,2.6-5.8,5.8-5.8v2.2c-2,0.1-3.5,1.8-3.4,3.7
c0.1,1.8,1.5,3.3,3.4,3.4h99.2c2,0.1,3.7-1.3,3.8-3.3c0.1-2-1.3-3.7-3.3-3.8c-0.1,0-0.2,0-0.3,0h-0.2v-2.2c3.2-0.1,5.9,2.4,6,5.6
s-2.4,5.9-5.6,6C121.1,33.1,121,33.1,120.8,33.1"/>
<path class="st2" d="M21.6,21.5l36.1,1.1l-36.1,1.1V21.5z"/>
<path class="st2" d="M125.5,23.8l-45.1-1.1l45.1-1.1V23.8z"/>
<rect x="-2.5" y="-1.1" class="st3" width="571.3" height="131.4"/>
<path class="st2" d="M163.8,91.7l7.3-10.9c5.8,5.5,14.3,9.3,22.3,9.3c7.1,0,13.1-3.3,13.1-8.3c0-6-8.1-7.9-15.4-9.6
c-13.7-3.2-24.8-9.8-24.8-22.3c0-12.7,11.1-21,27.1-21c10.7,0,19.4,3.7,24.7,8.9l-6.6,10.8c-4-3.9-11.2-6.9-18.3-6.9
s-12.2,3.2-12.2,7.7c0,5.5,7.4,7.9,14.1,9.3s26.2,6.2,26.2,22.5c0,12.8-12.2,21.6-27.8,21.6C182.6,102.8,171.1,98.4,163.8,91.7z"/>
<path class="st2" d="M281.7,80.1h-40.9c1.9,6.6,7.5,10.9,15.1,10.9c5.6,0.1,10.9-2.3,14.5-6.5l9,7.9c-5.5,6.5-14,10.5-23.9,10.5
c-16.8,0-29.3-12-29.3-27.8c0-15.6,12.1-27.4,28-27.4S282,59.4,282,75.3C282,76.9,281.9,78.5,281.7,80.1z M240.8,70.3h26.9
c-1.7-6.6-6.9-10.9-13.4-10.9C247.7,59.4,242.5,63.8,240.8,70.3L240.8,70.3z"/>
<path class="st2" d="M321.3,48v13.9h-2.3c-9.6,0-15.2,5.7-15.2,14.7v25h-13.4V48.9h13.5v6.8c3.6-4.8,9.2-7.7,15.2-7.7L321.3,48z"/>
<path class="st2" d="M381.9,48.9L360,101.6h-13.9l-21.9-52.8h15.3l13.8,35.9L367,48.9H381.9z"/>
<path class="st2" d="M437.1,80.1h-40.9c1.9,6.6,7.5,10.9,15.1,10.9c5.6,0.1,10.9-2.3,14.5-6.5l9,7.9c-5.5,6.5-14,10.5-23.9,10.5
c-16.8,0-29.3-12-29.3-27.8c0-15.6,12.1-27.4,28-27.4s27.7,11.8,27.7,27.7C437.4,76.9,437.3,78.5,437.1,80.1z M396.1,70.3H423
c-1.7-6.6-6.9-10.9-13.4-10.9S397.7,63.8,396.1,70.3L396.1,70.3z"/>
<path class="st2" d="M476.7,48v13.9h-2.2c-9.6,0-15.2,5.7-15.2,14.7v25h-13.5V48.9h13.5v6.8c3.6-4.8,9.2-7.7,15.2-7.7L476.7,48z"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="870.0443" y1="434.2369" x2="907.1767" y2="465.2789" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
<stop offset="0" style="stop-color:#FEAF6F"/>
<stop offset="1" style="stop-color:#FD5E83"/>
</linearGradient>
<path class="st4" d="M492.5,100.6V87c3.2,1.4,6.6,2.1,10,2.2c7.3,0,11.8-3.9,11.8-10.9v-48h14.3V79c0,15-9.8,23.9-24.5,23.9
C500,102.9,496.1,102.1,492.5,100.6z"/>
<linearGradient id="SVGID_00000162328622213414588160000008200821717462734513_" gradientUnits="userSpaceOnUse" x1="920.7661" y1="434.5518" x2="972.3098" y2="477.6348" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
<stop offset="0" style="stop-color:#FEAF6F"/>
<stop offset="1" style="stop-color:#FD5E83"/>
</linearGradient>
<path style="fill:url(#SVGID_00000162328622213414588160000008200821717462734513_);" d="M593.2,48.9v52.8h-13.5v-6.3
c-4.4,4.9-10.6,7.6-17.2,7.5c-14.7,0-25.8-11.9-25.8-27.6s11.1-27.6,25.8-27.6c6.5-0.1,12.8,2.7,17.2,7.5v-6.3L593.2,48.9z
M579.8,75.2c0-8-6.6-14.5-14.6-14.5c-8,0-14.5,6.6-14.5,14.6c0,8,6.5,14.4,14.5,14.5c7.9,0.2,14.4-6,14.6-13.9
C579.8,75.7,579.8,75.5,579.8,75.2z"/>
<linearGradient id="SVGID_00000026849485640012965730000014957007722205225107_" gradientUnits="userSpaceOnUse" x1="973.2171" y1="437.9167" x2="1007.0711" y2="466.2133" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
<stop offset="0" style="stop-color:#FEAF6F"/>
<stop offset="1" style="stop-color:#FD5E83"/>
</linearGradient>
<path style="fill:url(#SVGID_00000026849485640012965730000014957007722205225107_);" d="M635.9,48v13.9h-2.3
c-9.6,0-15.2,5.7-15.2,14.7v25H605V48.9h13.4v6.8c3.6-4.8,9.2-7.7,15.2-7.7L635.9,48z"/>
<linearGradient id="SVGID_00000011000279650532451330000005619277557075874698_" gradientUnits="userSpaceOnUse" x1="1015.3561" y1="439.477" x2="1056.9301" y2="474.2302" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
<stop offset="0" style="stop-color:#FEAF6F"/>
<stop offset="1" style="stop-color:#FD5E83"/>
</linearGradient>
<path style="fill:url(#SVGID_00000011000279650532451330000005619277557075874698_);" d="M638.7,94.8l6.5-8.9
c4.2,3.8,9.7,5.9,15.4,5.9c5.4,0,9.3-1.8,9.3-5c0-3.5-4.6-4.8-10.3-6.1c-8.4-1.9-19.2-4.5-19.2-16.5c0-11.2,9.8-16.7,21.5-16.7
c7.4-0.1,14.6,2.3,20.5,6.9l-6.5,9c-3.9-3.1-8.7-4.8-13.7-4.9c-4.6,0-8.3,1.5-8.3,4.5c0,3.5,4.4,4.7,10.3,5.9
c8.4,1.9,19.2,4.5,19.2,16.4c0,11.2-9.9,17.3-22.6,17.3C652.9,102.9,644.9,100.1,638.7,94.8z"/>
<linearGradient id="SVGID_00000176732902084481618460000012775063734620060048_" gradientUnits="userSpaceOnUse" x1="408.7259" y1="431.5905" x2="485.4144" y2="495.6844" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
<stop offset="0" style="stop-color:#FEAF6F"/>
<stop offset="1" style="stop-color:#FD5E83"/>
</linearGradient>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000176732902084481618460000012775063734620060048_);" d="
M124.5,62c-12.7,0.9-27,5.5-35.7,12.3c-38.7,30.3-69.2-6.6-69.3-6.6l6.8,36.8c0.8,4.3,4.6,7.5,9,7.5l73,0.2c4.5,0,8.3-3.2,9.1-7.6
L124.5,62z"/>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 142.71 128.36"><defs><style>.cls-1{fill:#fff;}.cls-2{fill-rule:evenodd;fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="408.73" y1="431.59" x2="485.41" y2="495.68" gradientTransform="translate(-374.6 -381.38)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#feaf6f"/><stop offset="1" stop-color="#fd5e83"/></linearGradient></defs><path class="cls-1" d="M133.09,19.17H9.67A3.24,3.24,0,0,1,6.46,16V3.24A3.24,3.24,0,0,1,9.7,0H133.09a3.25,3.25,0,0,1,3.25,3.24V16a3.25,3.25,0,0,1-3.25,3.24"/><path class="cls-1" d="M23.61,36.67A11.41,11.41,0,0,0,14.8,41a15.79,15.79,0,0,0-3.25,12.8l9.18,51.92c1.17,6.62,6.25,11.42,12.06,11.42H110c5.82,0,10.89-4.8,12.06-11.42l9.18-51.91A15.86,15.86,0,0,0,128,41a11.5,11.5,0,0,0-8.82-4.33ZM110,128.35H32.8c-11.27,0-21-8.7-23.12-20.69L.46,55.75a26.72,26.72,0,0,1,5.71-22,22.77,22.77,0,0,1,17.41-8.34h95.56a22.8,22.8,0,0,1,17.41,8.34,26.79,26.79,0,0,1,5.71,22l-9.19,51.91c-2.12,12-11.84,20.7-23.12,20.7"/><path class="cls-1" d="M120.8,23.76V21.51A3.56,3.56,0,0,0,121,14.4H21.59a3.56,3.56,0,0,0,0,7.11v2.25a5.81,5.81,0,0,1,0-11.61H120.8a5.81,5.81,0,0,1,.48,11.61h-.48"/><path class="cls-1" d="M120.8,33.11H21.59a5.8,5.8,0,0,1,0-11.6v2.24a3.56,3.56,0,0,0,0,7.11H120.8a3.56,3.56,0,0,0,.52-7.1h-.52V21.51a5.81,5.81,0,0,1,.48,11.61,3.84,3.84,0,0,1-.48,0"/><path class="cls-1" d="M21.59,21.51l36.13,1.13L21.59,23.76Z"/><path class="cls-1" d="M125.46,23.76,80.35,22.64l45.11-1.13Z"/><path class="cls-2" d="M124.46,62c-12.72.93-27,5.55-35.7,12.34-38.69,30.34-69.25-6.6-69.28-6.58l6.75,36.83a9.16,9.16,0,0,0,9,7.52l73,.16a9.17,9.17,0,0,0,9.06-7.64Z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,46 @@
// This is the "Offline page" service worker
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
const CACHE = "crafty-controller";
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = "/offline";
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
self.addEventListener('install', async (event) => {
event.waitUntil(
caches.open(CACHE)
.then((cache) => cache.add(offlineFallbackPage))
);
});
if (workbox.navigationPreload.isSupported()) {
workbox.navigationPreload.enable();
}
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResp = await event.preloadResponse;
if (preloadResp) {
return preloadResp;
}
const networkResp = await fetch(event.request);
return networkResp;
} catch (error) {
const cache = await caches.open(CACHE);
const cachedResp = await cache.match(offlineFallbackPage);
return cachedResp;
}
})());
}
});

View File

@ -18,6 +18,16 @@
href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" /> href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" />
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css"> <link rel="stylesheet" href="/static/assets/css/crafty.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<link rel="shortcut icon" sizes="192x192" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
@ -247,7 +257,7 @@
const sendWssError = () => wsOpen || warn( const sendWssError = () => wsOpen || warn(
'WebSockets are required for Crafty to work. This websocket connection has been closed. Are you using a reverse proxy?', 'WebSockets are required for Crafty to work. This websocket connection has been closed. Are you using a reverse proxy?',
'https://wiki.craftycontrol.com/en/4/docs/Reverse%20Proxy%20Examples', 'https://docs.craftycontrol.com/pages/getting-started/proxies/',
'wssError' 'wssError'
) )
@ -526,6 +536,14 @@
}); });
}); });
$(document).ready(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.log('Service Worker Registered');
});
}
});
</script> </script>
{% block js %} {% block js %}

View File

@ -14,6 +14,13 @@
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css"> <link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css">
<link rel="stylesheet" href="/static/assest/css/crafty.css"> <link rel="stylesheet" href="/static/assest/css/crafty.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->

View File

@ -100,7 +100,7 @@
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="https://wiki.craftycontrol.com" target="_blank"> <a class="nav-link" href="https://docs.craftycontrol.com" target="_blank">
<i class="fas fa-book"></i> &nbsp; <i class="fas fa-book"></i> &nbsp;
<span class="menu-title">{{ translate('sidebar', 'documentation', data['lang']) }}</span> <span class="menu-title">{{ translate('sidebar', 'documentation', data['lang']) }}</span>
</a> </a>
@ -109,7 +109,7 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/panel/wiki"> <a class="nav-link" href="/panel/wiki">
<i class="fa fa-info-circle"></i> &nbsp; <i class="fa fa-info-circle"></i> &nbsp;
<span class="menu-title">Wiki</span> <span class="menu-title">{{ translate('sidebar', 'inApp', data['lang']) }}</span>
</a> </a>
</li> </li>

View File

@ -6,8 +6,7 @@
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %} {% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
{% block content %} {% block content %}
<link rel="stylesheet" <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
<div class="content-wrapper"> <div class="content-wrapper">
@ -30,7 +29,12 @@
<div class="card-body"> <div class="card-body">
{% if data['superuser'] %} {% if data['superuser'] %}
{% include "parts/crafty_config_list.html %} <span class="d-none d-sm-block">
{% include "parts/crafty_config_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_crafty_config_list.html %}
</span>
{% end %} {% end %}
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
@ -69,11 +73,8 @@
</select> </select>
{% elif item[0] == 'disabled_language_files' %} {% elif item[0] == 'disabled_language_files' %}
<div class="input-group"> <div class="input-group">
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) { <button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{ translate('panelConfig', 'enableLang', data['lang']) }}</button>
$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh') <select id="lang_select" class="form-control selectpicker show-tick custom-picker" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
});">{{ translate('panelConfig', 'enableLang', data['lang']) }}</button>
<select id="lang_select" class="form-control selectpicker show-tick" data-icon-base="fas"
data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for lang in data['all_languages'] %} {% for lang in data['all_languages'] %}
{% if lang in item[1] %} {% if lang in item[1] %}
<option selected>{{lang}}</option> <option selected>{{lang}}</option>
@ -82,17 +83,12 @@
{% end %} {% end %}
{% end %} {% end %}
</select> </select>
<textarea id="disabled_lang" name="{{item[0]}}" class="form-control list hidden" <textarea id="disabled_lang" name="{{item[0]}}" class="form-control list hidden" rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}" hidden>{{','.join(item[1])}}</textarea>
rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}"
hidden>{{','.join(item[1])}}</textarea>
</div> </div>
{% elif item[0] == 'monitored_mounts'%} {% elif item[0] == 'monitored_mounts'%}
<div class="input-group"> <div class="input-group">
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#mount_select')).each(function(element) { <button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#mount_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{ translate('panelConfig', 'noMounts', data['lang']) }}</button>
$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh') <select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
});">{{ translate('panelConfig', 'noMounts', data['lang']) }}</button>
<select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas"
data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for mount in data['all_partitions'] %} {% for mount in data['all_partitions'] %}
{% if mount in item[1] %} {% if mount in item[1] %}
<option selected>{{mount}}</option> <option selected>{{mount}}</option>
@ -101,13 +97,10 @@
{% end %} {% end %}
{% end %} {% end %}
</select> </select>
<textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden" <textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden" rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}" hidden>{{','.join(item[1])}}</textarea>
rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}"
hidden>{{','.join(item[1])}}</textarea>
</div> </div>
{% elif isinstance(item[1], list) %} {% elif isinstance(item[1], list) %}
<textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}" <textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}" class="form-control list">{{','.join(item[1])}}</textarea>
class="form-control list">{{','.join(item[1])}}</textarea>
{% elif isinstance(item[1], bool) %} {% elif isinstance(item[1], bool) %}
<div style="margin-left: 30px;"> <div style="margin-left: 30px;">
{% if item[1] == True %} {% if item[1] == True %}
@ -123,11 +116,9 @@
{% end %} {% end %}
</div> </div>
{% elif isinstance(item[1], int) %} {% elif isinstance(item[1], int) %}
<input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" <input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="1" min="0" required>
step="1" min="0" required>
{% else %} {% else %}
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" <input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="2" min="0" required>
step="2" min="0" required>
{% end %} {% end %}
</div> </div>
{% end %} {% end %}
@ -142,10 +133,6 @@
</div> </div>
<style> <style>
.custom-picker {
border: 1px solid var(--outline);
}
.dropdown-menu.inner { .dropdown-menu.inner {
display: inline-block !important; display: inline-block !important;
} }

View File

@ -28,7 +28,12 @@
{% if data['superuser'] %} {% if data['superuser'] %}
{% include "parts/crafty_config_list.html %} <span class="d-none d-sm-block">
{% include "parts/crafty_config_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_crafty_config_list.html %}
</span>
{% end %} {% end %}
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
@ -51,16 +56,19 @@
<div class="col-12"> <div class="col-12">
<h4>{{ translate('customLogin', 'loginImage', data['lang']) }}</h4> <h4>{{ translate('customLogin', 'loginImage', data['lang']) }}</h4>
<hr> <hr>
<form class="form-row" name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)"> <form class="form" name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type"> <input type="hidden" value="import_zip" name="create_type">
<div class="col form-group"> <div class="form-group">
<span id="upload_input"><input type="file" class="form-control-file" id="file" name="file" <div id="upload_input" class="input-group">
multiple="false" required></span> <div class="custom-file">
</div> <input type="file" class="custom-file-input" id="file" name="file" multiple="false" required>
<div class="col form-group"> <label id="fileLabel" class="custom-file-label" for="file">{{ translate('customLogin', 'labelLoginImage', data['lang']) }}</label>
<button type="button" class="btn btn-info" id="upload-button" onclick="sendFile()" </div>
disabled>UPLOAD</button> <div class="input-group-append">
<button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()" disabled>UPLOAD</button>
</div>
</div>
</div> </div>
</form> </form>
<hr> <hr>
@ -73,8 +81,7 @@
<div class="form-group row"> <div class="form-group row">
<label for="photo" class="col-sm-6 col-form-label">Selected Background Image</label> <label for="photo" class="col-sm-6 col-form-label">Selected Background Image</label>
<div class="col-sm-6"> <div class="col-sm-6">
<select class="form-select form-control form-control-lg select-css form-control-plaintext" <select class="form-select form-control form-control-lg select-css form-control-plaintext" id="photo" name="photo" form="photo_form" onchange="updateBackgroundPreview()">
id="photo" name="photo" form="photo_form" onchange="updateBackgroundPreview()">
{% for image in data["backgrounds"] %} {% for image in data["backgrounds"] %}
<option value="{{image}}">{{image}}</option> <option value="{{image}}">{{image}}</option>
{% end %} {% end %}
@ -83,9 +90,7 @@
</div> </div>
<div id="photo_loading" class="form-group" hidden> <div id="photo_loading" class="form-group" hidden>
<div class="progress"> <div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" <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>
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">&nbsp;<i
class="fa-solid fa-spinner"></i></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
@ -93,13 +98,11 @@
data['lang']) }}</label> data['lang']) }}</label>
<label class="col-sm-1" id="opacityValue">{{ data['login_opacity'] }}%</label> <label class="col-sm-1" id="opacityValue">{{ data['login_opacity'] }}%</label>
<div class="range col-sm-8"> <div class="range col-sm-8">
<input type="range" class="form-control-range" id="modal_opacity" name="modal_opacity" <input type="range" class="form-control-range" id="modal_opacity" name="modal_opacity" onchange="previewOpacity()" min="0" max="100" value="{{ data['login_opacity'] }}">
onchange="previewOpacity()" min="0" max="100" value="{{ data['login_opacity'] }}">
</div> </div>
</div> </div>
<div id="login_preview" style="position: relative;"> <div id="login_preview" style="position: relative;">
<img id="bg-preview" src="../../static/assets/images/auth/{{ data['background'] }}" <img id="bg-preview" src="../../static/assets/images/auth/{{ data['background'] }}" class="img-fluid" alt="Responsive image">
class="img-fluid" alt="Responsive image">
<div id="login-form-preview"> <div id="login-form-preview">
<div id="login-form-background" class="auto-form-wrapper login-modal"> <div id="login-form-background" class="auto-form-wrapper login-modal">
<div class="text-center auto-form-logo"> <div class="text-center auto-form-logo">
@ -163,20 +166,17 @@
</style> </style>
<div id="login_form_data"> <div id="login_form_data">
<input type="hidden" name="_xsrf" <input type="hidden" name="_xsrf" value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
<div class="form-group"> <div class="form-group">
<label class="label">Username</label> <label class="label">Username</label>
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control login-text-input login-input" <input type="text" class="form-control login-text-input login-input" placeholder="Username" name="username" id="username" required="true" disabled>
placeholder="Username" name="username" id="username" required="true" disabled>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="label">Password</label> <label class="label">Password</label>
<div class="input-group"> <div class="input-group">
<input type="password" class="form-control login-text-input login-input" <input type="password" class="form-control login-text-input login-input" placeholder="Password" name="password" id="password" required="true" disabled>
placeholder="Password" name="password" id="password" required="true" disabled>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -195,8 +195,7 @@
<a href="#" class="text-small forgot-password" disabled>Forgot Password</a> <a href="#" class="text-small forgot-password" disabled>Forgot Password</a>
</div> </div>
<div class="text-block text-center my-3"> <div class="text-block text-center my-3">
<span class="text-small font-weight-semibold"><a <span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control
href="https://craftycontrol.com/">Crafty Control
4.0.20</a> </span> 4.0.20</a> </span>
</div> </div>
</div> </div>
@ -228,6 +227,10 @@
</div> </div>
<style> <style>
.img-fluid {
margin-bottom: 1rem;
}
.popover-body { .popover-body {
color: white !important; color: white !important;
; ;
@ -272,6 +275,7 @@
console.log("File changed"); console.log("File changed");
if ($('#file').val()) { if ($('#file').val()) {
$('#upload-button').prop("disabled", false); $('#upload-button').prop("disabled", false);
document.getElementById("fileLabel").innerHTML = $('#file').val().split('\\').pop().split('/').pop();
console.log("File changed good"); console.log("File changed good");
} }
}); });
@ -352,7 +356,7 @@
var file; var file;
function sendFile() { function sendFile() {
file = $("#file")[0].files[0] file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div id="upload-progress-bar" 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>'; document.getElementById("upload_input").innerHTML = '<div class="progress" style="width: 100%"><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 xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf") let token = getCookie("_xsrf")
let fileName = file.name let fileName = file.name
@ -380,7 +384,7 @@
xmlHttpRequest.addEventListener('load', (event) => { xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') { if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!') console.log('Upload for file', file.name, 'was successful!')
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>'; document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
setTimeout(function () { setTimeout(function () {
window.location.reload(); window.location.reload();
}, 2000); }, 2000);

View File

@ -647,10 +647,13 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + server_id, url: `/api/v2/servers/${server_id}/action/${command}`,
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);
if (command === "clone_server" && data.status === "ok") {
window.location.reload();
}
/*setTimeout(function () { /*setTimeout(function () {
if (command != 'start_server') { if (command != 'start_server') {
location.reload(); location.reload();
@ -705,24 +708,6 @@
document.querySelector('.dynamicMsg').appendChild(parentEl); document.querySelector('.dynamicMsg').appendChild(parentEl);
} }
function send_kill(server_id) {
/* this getCookie function is in base.html */
const token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/kill?id=' + server_id,
success: function (data) {
console.log("got response:");
console.log(data);
/*setTimeout(function () {
location.reload();
}, 10000);*/
}
});
}
function update_one_server_status(server) { function update_one_server_status(server) {
/* Mobile view update */ /* Mobile view update */
server_cpu = document.getElementById('server_cpu_' + server.id); server_cpu = document.getElementById('server_cpu_' + server.id);
@ -901,17 +886,11 @@
}, },
callback: function (result) { callback: function (result) {
if (result) { if (result) {
send_kill(server_id); send_command(server_id, "kill_server");
let dialog = bootbox.dialog({ let dialog = bootbox.dialog({
title: '{% raw translate("dashboard", "killing", data["lang"]) %}', title: '{% raw translate("dashboard", "killing", data["lang"]) %}',
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>' message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
}); });
dialog.init(function () {
setTimeout(function () {
location.reload();
}, 15000);
});
} }
} }
}); });
@ -1000,7 +979,13 @@
}, },
callback: function (result) { callback: function (result) {
if (result) { if (result) {
cloneServer(server_id); send_command(server_id, 'clone_server');
bootbox.dialog({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
closeButton: false,
});
} }
} }
@ -1008,16 +993,6 @@
}); });
}); });
function cloneServer(server_id) {
send_command(server_id, 'clone_server');
bootbox.dialog({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
closeButton: false,
});
}
</script> </script>
<script src="/static/assets/vendors/js/jquery-ui.js"></script> <script src="/static/assets/vendors/js/jquery-ui.js"></script>
<link rel="stylesheet" href="/static/assets/vendors/css/jquery-ui.css"> <link rel="stylesheet" href="/static/assets/vendors/css/jquery-ui.css">
@ -1069,12 +1044,12 @@
const token = getCookie("_xsrf") const token = getCookie("_xsrf")
$.ajax({ $.ajax({
type: "POST", type: "PATCH",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/send_order?order=' + id_string, url: `/api/v2/users/@me`,
data: { data: JSON.stringify({
order: id_string, server_order: id_string,
}, }),
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);

View File

@ -12,6 +12,15 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +33,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>
@ -77,6 +86,21 @@
<script src="/static/assets/js/shared/settings.js"></script> <script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script> <script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject --> <!-- endinject -->
<script>
$(document).ready(function () {
let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.error('Service Worker Registered');
});
}
});
</script>
</body> </body>
</html> </html>

View File

@ -28,7 +28,12 @@
{% if data['superuser'] %} {% if data['superuser'] %}
{% include "parts/crafty_config_list.html %} <span class="d-none d-sm-block">
{% include "parts/crafty_config_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_crafty_config_list.html %}
</span>
{% end %} {% end %}
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->

View File

@ -321,9 +321,60 @@
return r ? r[1] : undefined; return r ? r[1] : undefined;
} }
function gather_server_json() {
servers = [];
for (s = 0; s < page_servers.length; s++){
mask = ""
for (i = 0; i < permissions.length; i++){
if ($(`#permission_${page_servers[s].id}_${permissions[i]}`).prop('checked')){
mask += "1"
}else{
mask += "0"
}
}
servers.push(JSON.stringify({"id": page_servers[s].id, "permissions": mask}));
}
return servers;
}
$( document ).ready(function() { $( document ).ready(function() {
console.log( "ready!" ); console.log( "ready!" );
}); });
const roleId = new URLSearchParams(document.location.search).get('id');
$("#config_form").on("submit", async function (e) {
e.preventDefault();
var token = getCookie("_xsrf")
let configForm = document.getElementById("config_form");
let formData = new FormData(configForm);
//Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries());
let send_object = Object()
send_object.servers = []
send_object.name = formDataObject.role_name
// Format the plain form data as JSON
let formDataJsonString = JSON.stringify(formDataObject, replacer);
let res = await fetch(`/api/v2/roles/${roleId}`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: formDataJsonString,
});
let responseData = await res.json();
if (responseData.status === "ok") {
window.location.reload();
} else {
bootbox.alert({
title: responseData.error,
message: responseData.error_data
});
}
});
</script> </script>

View File

@ -1,17 +1,14 @@
<ul class="nav nav-tabs col-md-12 tab-simple-styled" role="tablist" style="margin-top: 0;"> <ul class="nav nav-tabs col-md-12 tab-simple-styled" role="tablist" style="margin-top: 0;">
<li class="nav-item term-nav-item"> <li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'panel_config' %}active{% end %}" href="/panel/panel_config" <a class="nav-link {% if data['active_link'] == 'panel_config' %}active{% end %}" href="/panel/panel_config" role="tab" aria-selected="false">
role="tab" aria-selected="false"> <i class="fas fa-wrench"></i>{{ translate('panelConfig', 'pageTitle', data['lang']) }}</a>
<i class="fa-solid fa-wrench"></i>{{ translate('panelConfig', 'pageTitle', data['lang']) }}</a>
</li> </li>
<li class="nav-item term-nav-item"> <li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'config_json' %}active{% end %}" href="/panel/config_json" <a class="nav-link {% if data['active_link'] == 'config_json' %}active{% end %}" href="/panel/config_json" role="tab" aria-selected="false">
role="tab" aria-selected="false"> <i class="fas fa-code"></i>{{ translate('panelConfig', 'json', data['lang']) }}</a>
<i class="fa-solid fa-code"></i>{{ translate('panelConfig', 'json', data['lang']) }}</a>
</li> </li>
<li class="nav-item term-nav-item"> <li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'custom_login' %}active{% end %}" href="/panel/custom_login" <a class="nav-link {% if data['active_link'] == 'custom_login' %}active{% end %}" href="/panel/custom_login" role="tab" aria-selected="false">
role="tab" aria-selected="false"> <i class="fas fa-palette"></i>{{ translate('panelConfig', 'custom', data['lang']) }}</a>
<i class="fa fa-palette"></i>{{ translate('panelConfig', 'custom', data['lang']) }}</a>
</li> </li>
</ul> </ul>

View File

@ -228,6 +228,24 @@
} }
initParser('input_motd', 'input_motd'); initParser('input_motd', 'input_motd');
let text = ""
let players = server.players_cache;
for(let i=0; i < players.length; i++){
text += `<tr id="playerItem-${ players[i]["name"] }" class="playerItem--" style="text-align: center;">`;
text += `<td class="no-scroll" style="overflow: scroll;"><strong>${players[i]["name"]}</strong></td>`;
if(players[i]["status"] === "Online"){
text += `<td><span class="text-success"><i class="fas fa-signal"></i> ${ players[i]['status'] }</span></td>`
}else{
text += `<td><span class="text-warning"><i class="fa-regular fa-circle-xmark"></i><span class="offline-status">&nbsp;${ players[i]['status'] }</span><span class="conn-break"> Last connection :<br> ${ players[i]['last_seen'] }</span></td>`
}
if(server["running"]){
text += `<td><button onclick="send_command_to_server('ban ${ players[i]['name'] }')" type="button" class="btn btn-danger controls">Ban</button><br class="mobile-break"><button onclick="send_command_to_server('kick ${ players[i]['name'] }')" type="button" class="btn btn-outline-danger controls">Kick</button><br><button onclick="send_command_to_server('op ${ players[i]['name'] }')" type="button" class="btn btn-warning controls">OP</button><br class="mobile-break"><button onclick="send_command_to_server('deop ${ players[i]['name'] }')" type="button" class="btn btn-outline-warning controls">De-OP</button></td>`
}else{
text += `<td><span> Unavailable<br> (Server Offline)</span></td>`
}
}
$("#player-body").html(text);
} }

View File

@ -0,0 +1,15 @@
<div class="col-sm-12 mt-4 mb-4">
<div class="dropdown">
<button class="btn btn-outline dropdown-toggle custom-picker" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false">
<i class="fas fa-bars"></i> Crafty Config
</button>
<div class="dropdown-menu col-md-12" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item {% if data['active_link'] == 'panel_config' %}active{% end %}" href="/panel/panel_config" role="tab" aria-selected="false">
<i class="fas fa-wrench"></i> Panel Config</a>
<a class="dropdown-item {% if data['active_link'] == 'config_json' %}active{% end %}" href="/panel/config_json" role="tab" aria-selected="false">
<i class="fas fa-code"></i> Config.json</a>
<a class="dropdown-item {% if data['active_link'] == 'custom_login' %}active{% end %}" href="/panel/custom_login" role="tab" aria-selected="false">
<i class="fas fa-palette"></i> Custom Login</a>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
<div class="col-sm-12 mt-4 mb-4"> <div class="col-sm-12 mt-4 mb-4">
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false"> <button class="btn btn-outline dropdown-toggle custom-picker" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false">
Server Controls <i class="fas fa-bars"></i> Server Controls
</button> </button>
<div class="dropdown-menu col-md-12" aria-labelledby="dropdownMenuButton"> <div class="dropdown-menu col-md-12" aria-labelledby="dropdownMenuButton">
{% if data['permissions']['Terminal'] in data['user_permissions'] %} {% if data['permissions']['Terminal'] in data['user_permissions'] %}
@ -12,7 +12,7 @@
<a class="dropdown-item {% if data['active_link'] == 'logs' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="false"><i class="fa-solid fa-book-open-reader"></i> {{ translate('serverDetails', 'logs', data['lang']) }}</a> <a class="dropdown-item {% if data['active_link'] == 'logs' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="false"><i class="fa-solid fa-book-open-reader"></i> {{ translate('serverDetails', 'logs', data['lang']) }}</a>
{% end %} {% end %}
{% if data['permissions']['Schedule'] in data['user_permissions'] %} {% if data['permissions']['Schedule'] in data['user_permissions'] %}
<a class="dropdown-item {% if data['active_link'] == 'schedules' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules" role="tab" aria-selected="false"><i class="fas fa-clock"></i> {{ translate('serverDetails', 'schedule', data['lang']) }}</a> <a class="dropdown-item {% if data['active_link'] == 'schedules' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules" role="tab" aria-selected="false"><i class="fa-solid fa-clock"></i> {{ translate('serverDetails', 'schedule', data['lang']) }}</a>
{% end %} {% end %}
{% if data['permissions']['Backup'] in data['user_permissions'] %} {% if data['permissions']['Backup'] in data['user_permissions'] %}
{% if data['backup_failed'] %} {% if data['backup_failed'] %}

View File

@ -0,0 +1,96 @@
<div class="col-xl-6 col-lg-12 col-md-12 col-sm-12">
<h2>{{ translate('serverPlayerManagement', 'players', data['lang']) }}:</h2>
<table class="table table-sm-responsive">
<thead class="thead">
<tr>
<th scope="col">Player</th>
<th scope="col">Status</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody id="player-body">
{% for player in data['cached_players'] %}
<tr id="playerItem-{{ player['name'] }}" class="playerItem--" style="text-align: center;">
<td>
<strong> {{ player['name'] }}</strong>
</td>
{% if player['status'] == 'Online' %}
<td style="overflow: scroll;"><span class="text-success"><i class="fas fa-signal"></i> {{ player['status'] }}</span></td>
{% elif player['status'] == 'Offline' %}
<td><span class="text-warning"><i class="fa-regular fa-circle-xmark"></i><span class="offline-status">&nbsp;{{ player['status'] }}</span><span class="conn-break"> Last connection :<br> {{ player['last_seen'] }}</span></span></td>
{% end %}
<td class="buttons" style="text-align: center;">
{% if data['server_stats']['running'] %}
<button onclick="send_command_to_server(`ban {{ player['name'] }}`)" type="button" class="btn btn-danger controls">Ban</button>
<br class="mobile-break"/>
<button onclick="send_command_to_server(`kick {{ player['name'] }}`)" type="button" class="btn btn-outline-danger controls">Kick</button>
<br>
<button onclick="send_command_to_server(`op {{ player['name'] }}`)" type="button" class="btn btn-warning controls">OP</button>
<br class="mobile-break"/>
<button onclick="send_command_to_server(`deop {{ player['name'] }}`)" type="button" class="btn btn-outline-warning controls">De-OP</button>
{% else %}
<span> Unavailable <br>(Server Offline)</span>
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
<style>
@media (min-width: 600px) {
.mobile-break { display: none;}
.offline-status {
display: none;
}
}
@media screen and (max-width: 600px) {
.conn-break { display: none; }
}
button.controls {
width: 70px;
}
</style>
<div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 no-scroll" width="100%">
<h2>{{ translate('serverPlayerManagement', 'bannedPlayers', data['lang']) }}:</h2>
<table class="table table-sm-responsive d-none d-lg-block no-scroll" style="width: 100%;">
<thead class="thead">
<tr>
<th scope="col">Player</th>
<th scope="col">Status</th>
<th scope="col">Reason</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for player in data['banned_players'] %}
<tr id="playerItem-{{ player }}" class="playerItem--">
<td><strong> {{ player['name'] }}</strong></td>
<td>Banned on {{ player['banned_on'] }}</td>
<td>Banned by : {{ player['source'] }} <br />Reason : {{ player['reason'] }}</td>
<td class="buttons">
<button onclick="send_command_to_server(`pardon {{ player['name'] }} `)" type="button" class="btn btn-danger">Unban</button>
</td>
</tr>
{% end %}
</tbody>
</table>
<table class="table table-sm-responsive d-block d-lg-none" style="width: 100%;">
<thead class="thead ">
<tr>
<th scope="col">Player</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for player in data['banned_players'] %}
<tr id="playerItem-{{ player }}" class="playerItem--">
<td><strong> {{ player['name'] }}</strong></td>
<td class="buttons">
<button onclick="send_command_to_server(`pardon {{ player['name'] }} `)" type="button" class="btn btn-danger">Unban</button>
</td>
</tr>
{% end %}
</tbody>
</table>
</div>

View File

@ -14,7 +14,8 @@
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title"> <h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }} {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4> </h4>
@ -39,63 +40,11 @@
</span> </span>
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> {% include "parts/server_players.html %}
<style>
.playerItem {
padding: 1rem;
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: space-between;
margin: 1rem 0px 1rem 0px;
}
.playerItem h3 {
vertical-align: middle;
padding: 0px;
margin: 0px;
margin-right: 1.5rem;
}
.playerItem button {
vertical-align: middle;
margin: 0.25rem;
}
.playerUnban {
margin-bottom: 1rem;
}
.banned span {
font-size: 1.1rem;
}
</style>
<h2>{{ translate('serverPlayerManagement', 'players', data['lang']) }}:</h2>
<ul style="list-style: none;padding: 0px;margin: 0px; margin-bottom: 1rem;gap: 1rem;">
{% for player in data['get_players'] %}
<li class="playerItem">
<h3>{{ player }}</h3>
<div class="buttons">
<button onclick="send_command_to_server('ban {{ player }}')" type="button" class="btn btn-danger">Ban</button>
<button onclick="send_command_to_server('kick {{ player }}')" type="button" class="btn btn-outline-danger">Kick</button>
<button onclick="send_command_to_server('op {{ player }}')" type="button" class="btn btn-warning">OP</button>
<button onclick="send_command_to_server('deop {{ player }}')" type="button" class="btn btn-outline-warning">De-OP</button>
</div>
</li>
{% end %}
</ul>
</div>
<div class="col-md-6 col-sm-12">
<h2>{{ translate('serverPlayerManagement', 'bannedPlayers', data['lang']) }}:</h2>
<ul id="bannedPlayers" style="list-style: none;padding: 0px;margin: 0px; margin-bottom: 1rem;gap: 1rem;">
<li class="playerItem banned">
<h3>{{ translate('serverPlayerManagement', 'loadingBannedPlayers', data['lang']) }}</h3>
</li>
</ul>
</div>
</div> </div>
<hr />
</div> </div>
</div> </div>
</div> </div>
@ -128,7 +77,7 @@
$(document).ready(function () { $(document).ready(function () {
console.log("ready!"); console.log("ready!");
var bannedPlayers = `{{ data['banned_players'] }}`; var bannedPlayers = `{{ data['banned_players_html'] }}`;
var bannedPlayersDecoded = htmlDecode(bannedPlayers); var bannedPlayersDecoded = htmlDecode(bannedPlayers);
@ -136,21 +85,22 @@
}); });
function send_command_to_server(command) { async function send_command_to_server(command) {
console.log(command) console.log(command)
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
console.log('sending command: ' + command) console.log('sending command: ' + command)
$.ajax({ let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
type: "POST", method: 'POST',
headers: { 'X-XSRFToken': token }, headers: {
url: '/ajax/send_command?id=' + serverId, 'X-XSRFToken': token
data: { command },
success: function (data) {
console.log("got response:");
console.log(data);
}, },
body: command,
}); });
let responseData = await res.text();
console.log("got response:");
console.log(responseData);
} }

View File

@ -326,7 +326,7 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/backup_now?id=' + server_id, url: `/api/v2/servers/${server_id}/action/backup_server`,
success: function (data) { success: function (data) {
return; return;
}, },

View File

@ -43,49 +43,36 @@
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<form class="forms-sample" method="post" id="config_form" action="/panel/server_detail"> <form class="forms-sample" method="post" id="config_form">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group"> <div class="form-group">
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small <label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small>
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small>
</label> </label>
<input type="text" class="form-control" name="server_name" id="server_name" <input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['server_name'] }}"
placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
</div> </div>
{% if data['super_user'] %} {% if data['super_user'] %}
<div class="form-group"> <div class="form-group">
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small <label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
</label> </label>
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<span style="color: gray; font-size: 12px;">{{ data['server_stats']['server_id']['path'] }}</span> <span style="color: gray; font-size: .9vw;">{{ data['server_stats']['server_id']['path'] }}</span>
🔒 🔒
</div> </div>
</div> </div>
{% if data['server_stats']['server_type'] != "minecraft-bedrock" %} {% if data['server_stats']['server_type'] != "minecraft-bedrock" %}
<div class="form-group"> <div class="form-group">
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small <label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang'])
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="log_path" id="log_path" <input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['log_path'] }}"
placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
</div> </div>
{% end %} {% end %}
<div class="form-group"> <div class="form-group">
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small <label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang'])
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="executable" id="executable" <input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['executable'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
</div> </div>
{% end %} {% end %}
{% if data['server_stats']['server_type'] == "minecraft-java" %} {% if data['server_stats']['server_type'] == "minecraft-java" %}
@ -94,10 +81,8 @@
<small class="text-muted ml-1">{{ translate('serverConfig', 'javaVersionDesc', data['lang']) <small class="text-muted ml-1">{{ translate('serverConfig', 'javaVersionDesc', data['lang'])
}}</small> }}</small>
</label> </label>
<select class="form-select form-control form-control-lg select-css" id="java_selection" <select class="form-select form-control form-control-lg select-css" id="java_selection" name="java_selection" form="config_form">
name="java_selection" form="config_form"> <option value="none">{{ translate('serverConfig', 'javaNoChange', data['lang'])}}</option>
<option value="">{{ translate('serverConfig',
'javaNoChange', data['lang'])}}</option>
{% for path in data['java_versions'] %} {% for path in data['java_versions'] %}
<option value="{{path}}">{{path}}</option> <option value="{{path}}">{{path}}</option>
{% end %} {% end %}
@ -110,33 +95,26 @@
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc', <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc',
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="text" class="form-control" name="execution_command" id="execution_command" <input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats']['server_id']['execution_command'] }}" placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['execution_command'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
</div> </div>
{% else %} {% else %}
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<span style="color: gray;">{{ data['server_stats']['server_id']['execution_command'] }}</span> 🔒 <span style="color: gray; font-size: .9vw;">{{ data['server_stats']['server_id']['execution_command'] }}</span> 🔒
</div> </div>
<br> <br>
{% end %} {% end %}
<div class="form-group"> <div class="form-group">
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small <label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang'])
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="stop_command" id="stop_command" <input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
value="{{ data['server_stats']['server_id']['stop_command'] }}"
placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }} <label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc', <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc',
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" <input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" required>
value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10"
required>
</div> </div>
{% if data['super_user'] %} {% if data['super_user'] %}
@ -145,31 +123,21 @@
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }} <label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang']) <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang'])
}}</small> </label> }}</small> </label>
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" <input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
value="{{ data['server_stats']['server_id']['executable_update_url'] }}"
placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
</div> </div>
{% end %} {% end %}
<div class="form-group"> <div class="form-group">
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small <label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small>
class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small>
</label> </label>
<input type="text" class="form-control" name="server_ip" id="server_ip" <input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small <label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}
</small> </label> </small> </label>
<input type="number" class="form-control" name="server_port" id="server_port" <input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" required>
value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" <span data-html="true" class="port-hint text-center" title="<i class='fal fa-exclamation-triangle'></i> " , data-content="{{
required> translate('serverConfig', 'statsHint1' , data['lang'])}} <br> <br> <strong>{{ translate('serverConfig', 'statsHint2', data['lang'])}}</strong>" , data-placement="right"></span>
<span data-html="true" class="port-hint text-center"
title="<i class='fal fa-exclamation-triangle'></i> " ,
data-content="{{
translate('serverConfig', 'statsHint1' , data['lang'])}} <br> <br> <strong>{{ translate('serverConfig', 'statsHint2', data['lang'])}}</strong>" ,
data-placement="right"></span>
</div> </div>
{% end %} {% end %}
@ -180,9 +148,7 @@
{{ data['server_stats']['server_id']['stop_command'] }}&nbsp;{{ translate('serverConfig', {{ data['server_stats']['server_id']['stop_command'] }}&nbsp;{{ translate('serverConfig',
'timeoutExplain2', data['lang']) }} 'timeoutExplain2', data['lang']) }}
</small> </label> </small> </label>
<input type="number" class="form-control" name="shutdown_timeout" id="shutdown_timeout" <input type="number" class="form-control" name="shutdown_timeout" id="shutdown_timeout" value="{{ data['server_stats']['server_id']['shutdown_timeout'] }}" step="2" max="300" min="60" required>
value="{{ data['server_stats']['server_id']['shutdown_timeout'] }}" step="2" max="300" min="60"
required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="ignored_exits">{{ translate('serverConfig', 'ignoredExits', data['lang']) }} <label for="ignored_exits">{{ translate('serverConfig', 'ignoredExits', data['lang']) }}
@ -190,57 +156,50 @@
data['lang']) data['lang'])
}} }}
</small> </label> </small> </label>
<input type="text" class="form-control" name="ignored_exits" id="ignored_exits" <input type="text" class="form-control" name="ignored_exits" id="ignored_exits" value="{{ data['server_stats']['server_id']['ignored_exits'] }}">
value="{{ data['server_stats']['server_id']['ignored_exits'] }}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc', <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc',
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" <input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" required>
value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0"
required>
</div> </div>
<div class="form-check-flat"> <div class="form-group">
<label for="auto_start" class="form-check-label ml-4 mb-4"> <div class="custom-control custom-switch">
{% if data['server_stats']['server_id']['auto_start'] %} {% if data['server_stats']['server_id']['auto_start'] %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked="" <input type="checkbox" class="custom-control-input" id="auto_start" name="auto_start" checked="" value="1">
data-toggle="toggle" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'serverAutoStart', <label class="custom-control-label" for="auto_start">&nbsp;&nbsp;{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}</label>
data['lang']) }}
{% else %} {% else %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1" <input type="checkbox" class="custom-control-input" id="auto_start" name="auto_start" value="1">
data-toggle="toggle">&nbsp;&nbsp;{{ <label class="custom-control-label" for="auto_start">&nbsp;&nbsp;{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}</label>
translate('serverConfig', 'serverAutoStart', data['lang']) }}
{% end %} {% end %}
</label> </div>
</div>
<label for="crash_detection" class="form-check-label ml-4 mb-4"> <div class="form-group">
<div class="custom-control custom-switch">
{% if data['server_stats']['server_id']['crash_detection'] %} {% if data['server_stats']['server_id']['crash_detection'] %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" <input type="checkbox" class="custom-control-input" id="crash_detection" name="crash_detection" checked="" value="1">
data-toggle="toggle" checked="" value="1">&nbsp;&nbsp;{{ translate('serverConfig', <label class="custom-control-label" for="crash_detection">&nbsp;&nbsp;{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}</label>
'serverCrashDetection', data['lang']) }}
{% else %} {% else %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" <input type="checkbox" class="custom-control-input" id="crash_detection" name="crash_detection" value="1">
data-toggle="toggle" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'serverCrashDetection', <label class="custom-control-label" for="crash_detection">&nbsp;&nbsp;{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}</label>
data['lang']) }}
{% end %} {% end %}
</label> </div>
</div>
{% if data['super_user'] %} <div class="form-group">
<label for="show_status" class="form-check-label ml-4 mb-4"> <div class="custom-control custom-switch">
{% if data['super_user'] %}
{% if data['server_stats']['server_id']['show_status'] %} {% if data['server_stats']['server_id']['show_status'] %}
<input type="checkbox" class="form-check-input" id="show_status" name="show_status" <input type="checkbox" class="custom-control-input" id="show_status" name="show_status" checked="" value="1">
data-toggle="toggle" checked="" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus', <label class="custom-control-label" for="show_status">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus', data['lang']) }}</label>
data['lang']) }}
{% else %} {% else %}
<input type="checkbox" class="form-check-input" id="show_status" name="show_status" <input type="checkbox" class="custom-control-input" id="show_status" name="show_status" value="1">&nbsp;&nbsp;
data-toggle="toggle" value="1">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus', <label class="custom-control-label" for="show_status">&nbsp;&nbsp;{{ translate('serverConfig', 'showStatus', data['lang']) }}</label>
data['lang']) }}
{% end %} {% end %}
</label> {% end %}
{% end %} </div>
</div> </div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ <button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{
@ -265,14 +224,10 @@
<div class="text-center"> <div class="text-center">
{% if data['server_stats']['running'] %} {% if data['server_stats']['running'] %}
{% if data['server_stats']['updating'] %} {% if data['server_stats']['updating'] %}
<i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% else %} {% else %}
<i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% end %} {% end %}
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang']) <a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang'])
@ -281,14 +236,10 @@
{% else %} {% else %}
{% if not data['failed'] %} {% if not data['failed'] %}
{% if data['server_stats']['updating'] %} {% if data['server_stats']['updating'] %}
<i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% else %} {% else %}
<i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button <i style="visibility: hidden;" id="update-spinner" class="fa fa-spinner fa-spin"></i>&nbsp;<button onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
onclick="send_command(serverId, 'update_executable');" id="update_executable" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}</button> 'update', data['lang']) }}</button>
{% end %} {% end %}
{% end %} {% end %}
@ -304,28 +255,26 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<style>
.toggle-handle {
background-color: white !important;
}
.toggle-on { <style>
.custom-control-input:checked~.custom-control-label::before {
color: black !important; color: black !important;
background-color: blueviolet !important; background-color: blueviolet !important;
border-color: var(--outline) !important;
} }
.toggle { .custom-control-label::before {
height: 0px !important; background-color: white !important;
background-color: grey !important; top: calc(-0.2rem);
}
.custom-switch .custom-control-label::after {
top: calc(-0.125rem + 1px);
} }
</style> </style>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
@ -359,7 +308,7 @@
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/delete_server?id=' + serverId, url: `/api/v2/servers/${serverId}`,
data: { data: {
}, },
success: function (data) { success: function (data) {
@ -373,7 +322,7 @@
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/delete_server_files?id=' + serverId, url: `/api/v2/servers/${serverId}?files=true`,
data: { data: {
}, },
success: function (data) { success: function (data) {
@ -393,7 +342,7 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + serverId, url: `/api/v2/servers/${serverId}/action/${command}`,
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);
@ -522,7 +471,7 @@
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/ajax/delete_unloaded_server?id=' + serverId, url: `/api/v2/servers/${serverId}`,
data: { data: {
}, },
success: function (data) { success: function (data) {
@ -550,11 +499,92 @@
$('.port-hint').popover("hide"); $('.port-hint').popover("hide");
}); });
async function postFormFieldsAsJson({ url, formData }) {
//Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries());
// Format the plain form data as JSON
let formDataJsonString = JSON.stringify(formDataObject);
//Set the fetch options (headers, body)
let fetchOptions = {
//HTTP method set to POST.
method: "PATCH",
//Set the headers that specify you're sending a JSON body request and accepting JSON response
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
// POST request body as JSON string.
body: formDataJsonString,
};
//Get the response body as JSON.
//If the response was not OK, throw an error.
let res = await fetch(url, fetchOptions);
//If the response is not ok throw an error (for debugging)
if (!res.ok) {
let error = await res.text();
throw new Error(error);
}
//If the response was OK, return the response body.
return res.json();
}
function replacer(key, value) {
if (key != "ignored_exits") {
if (typeof value == "boolean" || key === "executable_update_url") {
return value
} else {
return (isNaN(value) ? value : +value);
}
} else {
return value;
}
}
$(document).ready(function () { $(document).ready(function () {
let token = getCookie("_xsrf")
webSocket.on('remove_spinner', function () { webSocket.on('remove_spinner', function () {
document.getElementById("update-spinner").style.visibility = "hidden"; document.getElementById("update-spinner").style.visibility = "hidden";
}); });
$("#config_form").on("submit", async function (e) {
e.preventDefault();
var token = getCookie("_xsrf")
let configForm = document.getElementById("config_form");
let formData = new FormData(configForm);
//Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries());
//We need to make sure these are sent regardless of whether or not they're checked
formDataObject.show_status = $("#show_status").prop('checked');
formDataObject.crash_detection = $("#crash_detection").prop('checked');
formDataObject.auto_start = $("#auto_start").prop('checked');
console.log(formDataObject);
// Format the plain form data as JSON
let formDataJsonString = JSON.stringify(formDataObject, replacer);
formDataJsonString["ignored_exits"] = toString(formDataJsonString["ignored_exits"]);
console.log(formDataJsonString.ignored_exits)
console.log(formDataJsonString);
let res = await fetch(`/api/v2/servers/${serverId}`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: formDataJsonString,
});
let responseData = await res.json();
if (responseData.status === "ok") {
window.location.reload();
} else {
bootbox.alert({
title: responseData.error,
message: responseData.error_data
});
}
});
}); });
</script> </script>

View File

@ -37,15 +37,12 @@
<div class="row"> <div class="row">
<div class="col-md-8 col-sm-8"> <div class="col-md-8 col-sm-8">
{% if data['new_schedule'] == True %} {% if data['new_schedule'] == True %}
<form class="forms-sample" method="post" <form class="forms-sample" method="post" id="new_schedule_form"
action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}"> action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
{% else %} {% else %}
<form class="forms-sample" method="post" <form class="forms-sample" method="post" id="schedule_form"
action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}"> action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
{% end %} {% end %}
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group"> <div class="form-group">
<label for="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label> <label for="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label>
@ -89,7 +86,7 @@
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' , class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' ,
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="number" class="form-control" name="interval" id="interval" <input type="number" class="form-control" name="interval" id="interval"
value="{{ data['schedule']['interval'] }}" placeholder="Interval" required> value="{{ data['schedule']['interval'] }}" placeholder="Interval" required min="1">
<br> <br>
<br> <br>
<select id="interval_type" onchange="ifDays(this);" name="interval_type" <select id="interval_type" onchange="ifDays(this);" name="interval_type"
@ -108,7 +105,7 @@
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small <label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' , class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' ,
data['lang']) }}</small> </label> data['lang']) }}</small> </label>
<input type="time" class="form-control" name="time" id="time" <input type="time" class="form-control" name="start_time" id="time"
value="{{ data['schedule']['time'] }}" placeholder="Time" required> value="{{ data['schedule']['time'] }}" placeholder="Time" required>
</div> </div>
</div> </div>
@ -127,7 +124,7 @@
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small <label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang']) class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang'])
}}</small> </label> }}</small> </label>
<input type="input" class="form-control" name="cron" id="cron" <input type="input" class="form-control" name="cron_string" id="cron"
value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *"> value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
</div> </div>
</div> </div>
@ -234,8 +231,124 @@
return r ? r[1] : undefined; return r ? r[1] : undefined;
} }
function replacer(key, value) {
if (key != "start_time" && key != "cron_string" && key != "interval_type") {
if (typeof value == "boolean") {
return value
}
console.log(key)
if (key === "interval" && value === ""){
return 0;
}
if (key === "command" && typeof(value === "integer")){
return value.toString();
}else {
return (isNaN(value) ? value : +value);
}
} else {
if (value === "" && key == "start_time"){
return "00:00";
}else{
return value;
}
}
}
const serverId = new URLSearchParams(document.location.search).get('id');
const schId = new URLSearchParams(document.location.search).get('sch_id');
$(document).ready(function () { $(document).ready(function () {
console.log("ready!"); console.log("ready!");
$("#new_schedule_form").on("submit", async function (e) {
e.preventDefault();
var token = getCookie("_xsrf")
let schForm = document.getElementById("new_schedule_form");
let formData = new FormData(schForm);
formData.delete("difficulty");
//Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries());
//We need to make sure these are sent regardless of whether or not they're checked
formDataObject.enabled = $("#enabled").prop('checked');
formDataObject.one_time = $("#one_time").prop('checked');
if ($("#difficulty").val() == "reaction"){
formDataObject.interval_type = "reaction";
}
if ($("#action").val() != "command"){
formDataObject.command = formDataObject.action + "_server";
}
if (formDataObject.cron_string != ""){
formDataObject.interval_type = '';
}
console.log(formDataObject);
// Format the plain form data as JSON
let formDataJsonString = JSON.stringify(formDataObject, replacer);
let res = await fetch(`/api/v2/servers/${serverId}/tasks/`, {
method: 'POST',
headers: {
'X-XSRFToken': token,
"Content-Type": "application/json",
},
body: formDataJsonString,
});
let responseData = await res.json();
if (responseData.status === "ok") {
window.location.href = `/panel/server_detail?id=${serverId}&subpage=schedules`;
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
});
$("#schedule_form").on("submit", async function (e) {
e.preventDefault();
var token = getCookie("_xsrf")
let schForm = document.getElementById("schedule_form");
let formData = new FormData(schForm);
formData.delete("difficulty");
//Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries());
//We need to make sure these are sent regardless of whether or not they're checked
formDataObject.enabled = $("#enabled").prop('checked');
formDataObject.one_time = $("#one_time").prop('checked');
if ($("#difficulty").val() == "reaction"){
formDataObject.interval_type = "reaction";
}
if ($("#action").val() != "command"){
formDataObject.command = formDataObject.action + "_server";
}
if (formDataObject.cron_string != ""){
formDataObject.interval_type = '';
}
console.log(formDataObject);
// Format the plain form data as JSON
let formDataJsonString = JSON.stringify(formDataObject, replacer);
console.log(formDataJsonString);
let res = await fetch(`/api/v2/servers/${serverId}/tasks/${schId}`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token,
"Content-Type": "application/json",
},
body: formDataJsonString,
});
let responseData = await res.json();
if (responseData.status === "ok") {
window.location.href = `/panel/server_detail?id=${serverId}&subpage=schedules`;
} else {
bootbox.alert({
title: responseData.error,
message: responseData.error_data
});
}
});
}); });
@ -265,6 +378,7 @@
document.getElementById("parent").required = true; document.getElementById("parent").required = true;
document.getElementById("interval").required = false; document.getElementById("interval").required = false;
document.getElementById("time").required = false; document.getElementById("time").required = false;
$("#cron").val("");
} }
else { else {
document.getElementById("ifAdvanced").style.display = "none"; document.getElementById("ifAdvanced").style.display = "none";
@ -274,6 +388,7 @@
document.getElementById("parent").required = false; document.getElementById("parent").required = false;
document.getElementById("interval").required = true; document.getElementById("interval").required = true;
document.getElementById("time").required = true; document.getElementById("time").required = true;
$("#cron").val("");
} }
} }
function ifDays() { function ifDays() {
@ -286,22 +401,6 @@
} }
} }
function del_task(sch_id, id) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: { 'X-XSRFToken': token },
url: '/ajax/del_task?server_id=' + id + '&schedule_id=' + sch_id,
data: {
schedule_id: sch_id,
id: id
},
success: function (data) {
location.reload();
},
});
}
function startup() { function startup() {
try { try {
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true); document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);

View File

@ -47,18 +47,14 @@
<h4 class="card-title"><i class="fas fa-calendar"></i> {{ translate('serverSchedules', <h4 class="card-title"><i class="fas fa-calendar"></i> {{ translate('serverSchedules',
'scheduledTasks', data['lang']) }} </h4> 'scheduledTasks', data['lang']) }} </h4>
{% if data['user_data']['hints'] %} {% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" , <span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" , data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" , data-placement="bottom"></span>
data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" ,
data-placement="bottom"></span>
{% end %} {% end %}
<div><button <div>
onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`" <button onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`" class="btn btn-info">{{ translate('serverSchedules', 'create', data['lang']) }}<i class="fas fa-pencil-alt"></i></button>
class="btn btn-info">{{ translate('serverSchedules', 'create', data['lang']) }}<i </div>
class="fas fa-pencil-alt"></i></button></div>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" <table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" style="table-layout:fixed;">
style="table-layout:fixed;">
<thead> <thead>
<tr class="rounded"> <tr class="rounded">
<th style="width: 2%; min-width: 10px;">{{ translate('serverSchedules', 'name', data['lang']) }} <th style="width: 2%; min-width: 10px;">{{ translate('serverSchedules', 'name', data['lang']) }}
@ -87,10 +83,10 @@
<p>{{schedule.action}}</p> <p>{{schedule.action}}</p>
</td> </td>
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;"> <td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
<p>{{schedule.command}}</p> <p style="overflow: scroll;" class="no-scroll">{{schedule.command}}</p>
</td> </td>
<td id="{{schedule.interval}}" class="action"> <td id="{{schedule.interval}}" class="action">
{% if schedule.interval != '' %} {% if schedule.interval_type != '' and schedule.interval_type != 'reaction' %}
<p>{{ translate('serverSchedules', 'every', data['lang']) }}</p> <p>{{ translate('serverSchedules', 'every', data['lang']) }}</p>
<p>{{schedule.interval}} {{schedule.interval_type}}</p> <p>{{schedule.interval}} {{schedule.interval_type}}</p>
{% elif schedule.interval_type == 'reaction' %} {% elif schedule.interval_type == 'reaction' %}
@ -105,14 +101,10 @@
<p>{{schedule.next_run}}</p> <p>{{schedule.next_run}}</p>
</td> </td>
<td id="{{schedule.enabled}}" class="action"> <td id="{{schedule.enabled}}" class="action">
<input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle" <input style="width: 10px !important;" type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
data-schedule-id="{{schedule.schedule_id}}"
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
</td> </td>
<td id="{{schedule.action}}" class="action"> <td id="{{schedule.action}}" class="action">
<button <button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'"
class="btn btn-info">
<i class="fas fa-pencil-alt"></i> <i class="fas fa-pencil-alt"></i>
</button> </button>
<br> <br>
@ -126,8 +118,7 @@
</tbody> </tbody>
</table> </table>
<hr /> <hr />
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" <table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" style="table-layout:fixed;">
style="table-layout:fixed;">
<thead> <thead>
<tr class="rounded"> <tr class="rounded">
<th style="width: 25%; min-width: 50px;">{{ translate('serverSchedules', 'action', data['lang']) <th style="width: 25%; min-width: 50px;">{{ translate('serverSchedules', 'action', data['lang'])
@ -145,7 +136,7 @@
<p>{{schedule.action}}</p> <p>{{schedule.action}}</p>
</td> </td>
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;"> <td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
<p>{{schedule.command}}</p> <p style="overflow: scroll;">{{schedule.command}}</p>
</td> </td>
<td id="{{schedule.enabled}}" class="action"> <td id="{{schedule.enabled}}" class="action">
{% if schedule.enabled %} {% if schedule.enabled %}
@ -160,8 +151,7 @@
</td> </td>
</tr> </tr>
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" <div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -208,19 +198,14 @@
<p>zzzzzzz</p> <p>zzzzzzz</p>
{% end %} {% end %}
</li> </li>
<li id="{{schedule.enabled}}" class="action" <li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
style="border-top: .1em solid gray; border-bottom: .1em solid gray">
<h4>{{ translate('serverSchedules', 'enabled', data['lang']) }}</h4> <h4>{{ translate('serverSchedules', 'enabled', data['lang']) }}</h4>
<input type="checkbox" class="schedule-enabled-toggle" <input type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
data-schedule-id="{{schedule.schedule_id}}"
data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
</li> </li>
</ul> </ul>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button <button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'"
class="btn btn-info">
<i class="fas fa-pencil-alt"></i> {{ translate('serverSchedules', 'edit', data['lang']) <i class="fas fa-pencil-alt"></i> {{ translate('serverSchedules', 'edit', data['lang'])
}} }}
</button> </button>
@ -381,39 +366,6 @@
console.log("ready!"); console.log("ready!");
}); });
function yesnoCheck(that) {
if (that.value == "command") {
document.getElementById("ifYes").style.display = "block";
document.getElementById("command").required = true;
} else {
document.getElementById("ifYes").style.display = "none";
document.getElementById("command").required = false;
}
}
function basicAdvanced(that) {
if (that.value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
} else {
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifBasic").style.display = "block";
document.getElementById("interval").required = true;
document.getElementById("time").required = true;
}
}
function ifDays(that) {
if (that.value == "days") {
document.getElementById("ifDays").style.display = "block";
document.getElementById("time").required = true;
} else {
document.getElementById("ifDays").style.display = "none";
document.getElementById("time").required = false;
}
}
$(".del_button").click(function () { $(".del_button").click(function () {
var sch_id = $(this).data('sch'); var sch_id = $(this).data('sch');
@ -440,21 +392,19 @@
}); });
}); });
function del_task(sch_id, id) { async function del_task(sch_id, id) {
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
$.ajax({ let res = await fetch(`/api/v2/servers/${id}/tasks/${sch_id}`, {
type: "DELETE", method: 'DELETE',
headers: { 'X-XSRFToken': token }, headers: {
url: '/ajax/del_task?server_id=' + id + '&schedule_id=' + sch_id, 'token': token,
data: {
schedule_id: sch_id,
id: id
},
success: function (data) {
location.reload();
}, },
}); });
let responseData = await res;
if (responseData.statusText === "OK") {
window.location.reload();
}
} }
</script> </script>

View File

@ -41,21 +41,20 @@
</span> </span>
<div class="col-md-12"> <div class="col-md-12">
<button id="to-bottom" style="visibility: hidden; float: right;" class="btn btn-outline-success">{{ <button id="to-bottom" style="visibility: hidden; float: right;" class="btn btn-outline-success" hidden>{{
translate('serverDetails', 'reset', data['lang']) }}</button> translate('serverDetails', 'reset', data['lang']) }}</button>
<br />
<br />
<div class="input-group"> <div class="input-group">
<div id="virt_console" class="" <div id="virt_console" class=""
style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid var(--outline); background-color:var(--card-banner-bg);height:500px; overflow: scroll;"> style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid var(--outline); background-color:var(--card-banner-bg);height:500px; overflow: scroll;">
</div> </div>
</div> </div>
<br /> <br />
<div style="gap: 0.5rem;" class="input-group flex-wrap"> <div style="gap: 0.5rem;" class="input-group flex-wrap">
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command" <input style="min-width: 10rem;" type="text" class="form-control" id="server_command"
name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}"
autofocus=""> autofocus="">
<span class="input-group-btn ml-5"> <span class="input-group-btn ml-5">
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand', <button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand',
data['lang']) }}</button> data['lang']) }}</button>
@ -64,73 +63,73 @@
{% if data['permissions']['Commands'] in data['user_permissions'] %} {% if data['permissions']['Commands'] in data['user_permissions'] %}
{% if data['importing'] and data['server_stats']['running']%} {% if data['importing'] and data['server_stats']['running']%}
<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">
<button onclick="" id="start-btn" style="max-width: 7rem;" <button onclick="" id="start-btn" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled"><i 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> class="fa fa-spinner fa-spin"></i>&nbsp;{{translate('serverTerm', 'installing', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" <button onclick="" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
data['lang']) %}</button> data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" <button onclick="" id="stop-btn" style="max-width: 7rem;"
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
}}</button> }}</button>
</div> </div>
{% elif data['server_stats']['updating']%} {% 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">
<button onclick="" id="start-btn" style="max-width: 7rem;" <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;{{ class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i>&nbsp;{{
translate('serverTerm', 'updating', data['lang']) }}</button> translate('serverTerm', 'updating', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" <button onclick="" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
data['lang']) %}</button> data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" <button onclick="" id="stop-btn" style="max-width: 7rem;"
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
}}</button> }}</button>
</div> </div>
{% elif data['waiting_start'] %} {% elif data['waiting_start'] %}
<div id="control_buttons" <div id="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">
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" <button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;"
class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip"
title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm',
'starting', data['lang']) }}</button> 'starting', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" <button onclick="" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
data['lang']) %}</button> data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" <button onclick="" id="stop-btn" style="max-width: 7rem;"
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
}}</button> }}</button>
</div> </div>
{% elif data['importing'] %} {% elif data['importing'] %}
<div id="control_buttons" <div id="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">
<button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;" <button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;"
class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{ class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
translate('serverTerm', 'importing', translate('serverTerm', 'importing',
data['lang']) }}</button> data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" <button onclick="" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
data['lang']) %}</button> data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" <button onclick="" id="stop-btn" style="max-width: 7rem;"
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
}}</button> }}</button>
</div> </div>
{% else %} {% else %}
<div id="control_buttons" <div id="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">
<button onclick="send_command(serverId, 'start_server');" id="start-btn" style="max-width: 7rem;" <button onclick="send_command(serverId, 'start_server');" id="start-btn" style="max-width: 7rem;"
class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button> class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
<button onclick="send_command(serverId, 'restart_server');" id="restart-btn" style="max-width: 7rem;" <button onclick="send_command(serverId, 'restart_server');" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang']) class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang'])
%}</button> %}</button>
<button onclick="send_command(serverId, 'stop_server');" id="stop-btn" style="max-width: 7rem;" <button onclick="send_command(serverId, 'stop_server');" id="stop-btn" style="max-width: 7rem;"
class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button> class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div> </div>
{% end %} {% end %}
{% end %} {% end %}
@ -151,6 +150,8 @@
/* IE and Edge */ /* IE and Edge */
scrollbar-width: none; scrollbar-width: none;
/* Firefox */ /* Firefox */
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
white-space: pre-wrap;
} }
</style> </style>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
@ -179,7 +180,7 @@
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: { 'X-XSRFToken': token }, headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + serverId, url: `/api/v2/servers/${serverId}/action/${command}`,
success: function (data) { success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);
@ -311,12 +312,12 @@
formdata.append('command', serverCommand) formdata.append('command', serverCommand)
console.log('sending command: ' + serverCommand) console.log('sending command: ' + serverCommand)
let res = await fetch("/ajax/send_command?id=" + serverId, { let res = await fetch(`/api/v2/servers/${serverId}/stdin`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-XSRFToken': token 'X-XSRFToken': token
}, },
body: formdata, body: serverCommand,
}); });
let responseData = await res.text(); let responseData = await res.text();
@ -354,9 +355,11 @@
const elem = $(e.currentTarget); const elem = $(e.currentTarget);
if (Math.round(elem[0].scrollHeight - elem.scrollTop()) <= elem.outerHeight()) { if (Math.round(elem[0].scrollHeight - elem.scrollTop()) <= elem.outerHeight()) {
document.getElementById("to-bottom").style.visibility = "hidden"; document.getElementById("to-bottom").style.visibility = "hidden";
document.getElementById("to-bottom").hidden = true;
scrolled = false; scrolled = false;
} else { } else {
document.getElementById("to-bottom").style.visibility = "visible"; document.getElementById("to-bottom").style.visibility = "visible";
document.getElementById("to-bottom").hidden = false;
scrolled = true; scrolled = true;
} }
} }

View File

@ -13,14 +13,14 @@
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title">Wiki</h4> <h4 class="page-title">{{ translate('sidebar', 'documentation', data['lang']) }}</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12 grid-margin"> <div class="col-md-12 grid-margin">
<iframe src="https://wiki.craftycontrol.com" width=100% height=2200px title="crafty's wiki"></iframe> <iframe src="https://docs.craftycontrol.com/" width=100% height=1100px title="crafty's docs"></iframe>
</div> </div>

View File

@ -12,6 +12,14 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +32,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>

View File

@ -12,6 +12,14 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +32,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>

View File

@ -12,6 +12,13 @@
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css"> <link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- <meta name="apple-mobile-web-app-title" content="Crafty Controller 4"> -->
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -24,7 +31,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
background-position: center; background-position: center;
} }
@ -113,6 +120,11 @@
<span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control <span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control
{{data['version'] }}</a> </span> {{data['version'] }}</a> </span>
</div> </div>
<div class="text-block text-center my-3">
<a href="/status" class="text-small forgot-password">{{ translate('login', 'viewStatus',
data['lang']) }}</a>
</div>
</form> </form>
</div> </div>
@ -140,7 +152,15 @@
let login_opacity_div = document.getElementById('login_opacity'); let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value'); let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')'; document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.error('Service Worker Registered');
});
}
}); });
</script> </script>
</body> </body>

View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Crafty Controller</title>
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head>
<style>
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("/static/assets/images/auth/login_1.jpg");
background-size: cover;
}
</style>
<body class="dark-theme">
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
<div class="row w-100">
<div class="col-lg-4 mx-auto">
<div class="auto-form-wrapper">
<div class="text-center">
<img src="/static/assets/images/logo_long.svg"><br /><br />
<div class="col-sm-12 grid-margin stretch-card">
<div class="card card-statistics social-card facebook-card card-colored">
<div class="card-body">
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('offline', 'offline', data['lang']) }}</h4>
<h5 class="headline font-weight-medium"></h5>
<p class="mb-2 comment font-weight-light">
{{ translate('offline', 'pleaseConnect', data['lang']) }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
</div>
<!-- page-body-wrapper ends -->
</div>
<!-- container-scroller -->
<!-- plugins:js -->
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<!-- endinject -->
<!-- inject:js -->
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject -->
</body>
</html>

View File

@ -10,7 +10,7 @@
<style> <style>
.auth.auth-bg-1 { .auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"), background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg"); url("/static/assets/images/auth/login_1.jpg");
background-size: cover; background-size: cover;
} }
</style> </style>
@ -89,7 +89,7 @@
</div> </div>
<!-- View for Small screen --> <!-- View for Small screen -->
<div class="row justify-content-center align-items-sm-center"> <div class="row justify-content-center align-items-sm-center">
<div class="content-wrapper login-modal d-sm-none d-block"> <div class="content-wrapper login-modal d-sm-none d-block" style="background-color: var(--dropdown-bg);">
<img src="/static/assets/images/logo_long.png" style='width: 100%;'> <img src="/static/assets/images/logo_long.png" style='width: 100%;'>
<hr /> <hr />
{% if data['running'] != 0 %} {% if data['running'] != 0 %}

View File

@ -14,6 +14,13 @@
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css"> <link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css"> <link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css"> <link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Crafty">
<link rel="apple-touch-icon" href="../static/assets/images/Crafty_4-0.png">
<!-- endinject --> <!-- endinject -->
<!-- Plugin css for this page --> <!-- Plugin css for this page -->
<!-- End Plugin css for this page --> <!-- End Plugin css for this page -->
@ -98,7 +105,7 @@
usingWebSockets = false; usingWebSockets = false;
} }
// {% else %} // {% else %}
let usingWebSockets = false; usingWebSockets = false;
warn('WebSockets are not supported in Crafty if not using the https protocol') warn('WebSockets are not supported in Crafty if not using the https protocol')
var webSocket; var webSocket;
// {% end%} // {% end%}
@ -106,6 +113,21 @@
</script> </script>
{% block js %} {% block js %}
<!-- Custom js for this page --> <!-- Custom js for this page -->
<script>
$(document).ready(function () {
let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'})
.then(function (registration) {
console.error('Service Worker Registered');
});
}
});
</script>
<!-- End custom js for this page --> <!-- End custom js for this page -->
{% end %} {% end %}

View File

@ -31,50 +31,66 @@
</ul> </ul>
<div class="d-none" id="overlay" onclick="hide(event)"></div> <div class="d-none" id="overlay" onclick="hide(event)"></div>
<div class="row"> <div class="row">
{% if data['online'] %}
<div class="col-sm-6 grid-margin stretch-card"> <div class="col-sm-6 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4> <h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
<br /> <br />
<p class="card-description">
<form method="post" name="create_server" class="server-wizard" onSubmit="wait_msg()"> <form method="post" name="create_server" class="server-wizard" onSubmit="wait_msg()">
{% if data["server_api"] and data["online"] %}
<fieldset>
{% else %}
<fieldset disabled="disabled">
<style>
.api-alert{
position:absolute;
top:-5px;
left:0;
font-size: 50px !important;
color:#fff;
background:rgb(127, 133, 133);
opacity:.4;
width:100%;
height:100%;
z-index: 100;
}
.api-alert p {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
text-align: center;
font-size: 20px;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
</style>
{% end %}
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<div class="row"> <div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<div class="col-sm-12 col-md-12"> <input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name"
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
</div> </div>
<div class="col-sm-12"> <div class="form-group">
<div class="form-group"> <div id="accordion-1">
<div id="accordion-1"> <div class="card">
<div class="card"> <div class="card-header p-2" id="Role-1">
<div class="card-header p-2" id="Role-1"> <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" aria-controls="collapseRole-1">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
aria-controls="collapseRole-1"> <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} data['lang']) }}</small>
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', </p>
data['lang']) }}</small> </div>
</p> <div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent="">
</div> <div class="card-body scroll">
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent=""> <div class="form-group">
<div class="card-body scroll"> {% for r in data['roles'] %}
<div class="form-group"> <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
{% for r in data['roles'] %} {{ r['role_name'].capitalize() }}</label></span>
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" {% end %}
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -86,90 +102,70 @@
data['lang']) }}</button> data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) <button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
}}</button> }}</button>
</fieldset>
{% if not data["server_api"] and data["online"] %}
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
<p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: red;"></i>&nbsp;{{ translate('error', 'bedrockError', data['lang']) }}<a style="color: red;"; href="https://status.craftycontrol.com/status/craftycontrol"
target="_blank">&nbsp;{{ translate('error', 'craftyStatus', data['lang']) }}</a>
&nbsp;{{ translate('error', 'serverJars2', data['lang']) }}</p></div>
{% end %}
{% if not data["online"] %}
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
<p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: red;"></i>&nbsp;{{ translate('error', 'noInternet', data['lang']) }}</p></div>
{% end %}
</div> </div>
</form> </form>
</p>
</div> </div>
</div> </div>
{% end %}
<div class="col-sm-6 grid-margin stretch-card"> <div class="col-sm-6 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4> <h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
<br /> <br />
<p class="card-description">
<form method="post" class="server-wizard" onSubmit="wait_msg(true)"> <form method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" value="import_jar" name="create_type"> <input type="hidden" value="import_jar" name="create_type">
<div class="row"> <div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<div class="col-sm-12"> <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
<div class="form-group"> </div>
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label> <div class="form-group">
<input type="text" class="form-control" id="server_name" name="server_name" value="" <label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required> translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
</div> <input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server" required>
</div> </div>
<div class="form-group">
<div class="col-sm-12"> <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<div class="form-group"> <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{
translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path"
placeholder="/var/opt/server" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
placeholder="bedrock_server" required>
</div>
</div>
</div> </div>
<br /> <br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4> data['lang']) }}</small></h4>
<hr> <hr>
<div class="row"> <div class="form-group">
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }}
<div class="col-sm-12"> <small></small></label>
<div class="form-group"> <input type="number" class="form-control" id="port2" name="port" value="19132" step="1" min="1" max="65535" required>
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} </div>
<small></small></label> <div class="form-group">
<input type="number" class="form-control" id="port2" name="port" value="19132" step="1" min="1" <div id="accordion-2">
max="65535" required> <div class="card">
</div> <div class="card-header p-2" id="Role-2">
</div> <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
<div class="col-sm-12"> <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
<div class="form-group"> <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
<div id="accordion-2"> data['lang']) }}</small>
<div class="card"> </p>
<div class="card-header p-2" id="Role-2"> </div>
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" <div id="collapseRole-2" class="collapse" aria-labelledby="Role-2" data-parent="">
aria-controls="collapseRole-2"> <div class="card-body scroll">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <div class="form-group">
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', {% for r in data['roles'] %}
data['lang']) }}</small> <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
</p> {{ r['role_name'].capitalize() }}</label></span>
</div> {% end %}
<div id="collapseRole-2" class="collapse" aria-labelledby="Role-2" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -180,163 +176,118 @@
data['lang']) }}</button> data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) <button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
}}</button> }}</button>
</form> </form>
</p>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="col-md-6 grid-margin stretch-card">
<div class="row">
<div class="col-sm-6 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4> <h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
<br /> <br />
<p class="card-description">
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)"> <form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type"> <input type="hidden" value="import_zip" name="create_type">
<div class="row"> <div class="form-group">
<div class="col-sm-9"> <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<div class="col-sm-12"> <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value=""
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{
translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path"
placeholder="/var/opt/server.zip" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{
translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_files_button" type="button">{{
translate('serverWizard', 'clickRoot', data['lang']) }}</button>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
placeholder="bedrock_server" required>
</div>
</div>
</div>
</div> </div>
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{
translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip" required>
</div>
<div class="form-group">
<div class="col-sm-12"> <label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', <br>
data['lang']) }}</small></h4> <button class="btn btn-primary mr-2" id="root_files_button" type="button">{{
<hr> translate('serverWizard', 'clickRoot', data['lang']) }}</button>
<div class="row"> </div>
<div class="form-group">
<div class="col-sm-12"> <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<div class="form-group"> <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} </div>
<small></small></label> <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small>
<input type="number" class="form-control" id="port3" name="port" value="19132" step="1" min="1" </h4>
max="65535" required> <hr>
<div class="form-group">
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }}
<small></small></label>
<input type="number" class="form-control" id="port3" name="port" value="19132" step="1" min="1" max="65535" required>
</div>
<div class="form-group">
<div id="accordion-3">
<div class="card">
<div class="card-header p-2" id="Role-3">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang'])
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
</p>
</div> </div>
</div> <div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
<div class="card-body scroll">
<div class="col-sm-12"> <div class="form-group">
<div class="form-group"> {% for r in data['roles'] %}
<div id="accordion-3"> <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
<div class="card"> {{ r['role_name'].capitalize() }}</label></span>
<div class="card-header p-2" id="Role-3"> {% end %}
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true"
aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang'])
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
</p>
</div>
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-12" style="visibility: hidden;">
<div class="form-group">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
</div>
</div>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard',
'selectZipDir', data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path=""
style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input" name="root_path" value="" checked>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{{ translate('serverFiles', 'files', data['lang']) }}
</span>
</input>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverWizard', 'close', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
translate('serverWizard', 'save', data['lang']) }}</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button>
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
data['lang'])
}}</button>
</div> </div>
<div style="visibility: hidden;">
<div class="form-group">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
</div>
</div>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard',
'selectZipDir', data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input" name="root_path" value="" checked>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{{ translate('serverFiles', 'files', data['lang']) }}
</span>
</input>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverWizard', 'close', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
translate('serverWizard', 'save', data['lang']) }}</button>
</div>
</div>
</div>
</div>
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button>
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
data['lang'])
}}</button>
</form>
</div> </div>
</form>
</p>
</div> </div>
</div> </div>
<div class="col-sm-6 grid-margin stretch-card"> <div class="col-md-6 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -348,141 +299,115 @@
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type"> <input type="hidden" value="import_zip" name="create_type">
<div class="row"> <div class="form-group">
<div class="col-sm-12"> <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<div class="col-sm-12"> <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
<div class="form-group"> </div>
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value="" <div class="form-group">
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required> <label for="server">Server Upload </label>
</div> <div id="upload_input" class="input-group">
<div class="custom-file">
<input type="file" multiple="false" class="custom-file-input" id="file" name="file" required>
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('serverWizard', 'labelZipFile', data['lang']) }}</label>
</div> </div>
<div class="input-group-append">
<div class="col-sm-12"> <button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()" disabled>{{ translate('serverWizard',
<div class="form-group"> 'uploadButton', data['lang']) }}</button>
<label for="server">{{ translate('serverWizard', 'serverUpload', 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" onclick="sendFile()">{{ translate('serverWizard',
'uploadButton', data['lang']) }}</button>
</span>
</div>
</div> </div>
</div> </div>
<div id="lower_half" style="visibility: hidden;"> </div>
<div class="col-sm-12"> <div id="lower_half" style="visibility: hidden;">
<div class="form-group"> <div class="form-group">
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{ <label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{
translate('serverWizard', 'explainRoot', data['lang']) }}</small></label> translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
<br> <br>
<button class="btn btn-primary mr-2" id="root_upload_button" type="button">{{ <button class="btn btn-primary mr-2" id="root_upload_button" type="button">{{
translate('serverWizard', 'clickRoot', data['lang']) }}</button> translate('serverWizard', 'clickRoot', data['lang']) }}</button>
</div> </div>
</div>
<div class="form-group">
<div class="col-sm-12"> <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<div class="form-group"> <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label> </div>
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
placeholder="paper.jar" required> <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
</div> data['lang']) }}</small></h4>
</div> <hr>
<div class="col-sm-12"> <div class="form-group">
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small <label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
data['lang']) }}</small></h4> <input type="number" class="form-control" id="port4" name="port" value="19132" step="1" min="1" max="65535" required>
<hr> </div>
<div class="row">
<div class="form-group">
<div class="col-sm-12"> <div id="accordion-3">
<div class="form-group"> <div class="card">
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ <div class="card-header p-2" id="Role-3">
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label> <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
<input type="number" class="form-control" id="port4" name="port" value="19132" step="1" min="1" <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole',
max="65535" required> data['lang'])
</div> }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
</div> data['lang']) }}</small>
</p>
<div class="col-sm-12"> </div>
<div class="form-group"> <div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
<div id="accordion-3"> <div class="card-body scroll">
<div class="card"> <div class="form-group">
<div class="card-header p-2" id="Role-3"> {% for r in data['roles'] %}
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
aria-expanded="true" aria-controls="collapseRole-3"> {{ r['role_name'].capitalize() }}</label></span>
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', {% end %}
data['lang']) </div>
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', </div>
data['lang']) }}</small> </div>
</p> </div>
</div> </div>
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent=""> </div>
<div class="card-body scroll"> <div style="visibility: hidden;">
<div class="form-group"> <div class="form-group">
{% for r in data['roles'] %} <input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" </div>
type="checkbox">&nbsp; </div>
{{ r['role_name'].capitalize() }}</label></span> <div class="modal fade" id="dir_upload_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
{% end %} <div class="modal-dialog" role="document">
</div> <div class="modal-content">
</div> <div class="modal-header">
</div> <h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard',
</div> 'selectZipDir', data['lang']) }}</h5>
</div> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
</div> <span aria-hidden="true">&times;</span>
</div> </button>
<div class="col-sm-12" style="visibility: hidden;"> </div>
<div class="form-group"> <div class="modal-body">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path"> <div class="tree-ctx-item" id="main-tree-div-upload" data-path="" style="overflow: scroll; max-height:75%;">
</div> <input type="radio" id="main-tree-input-upload" name="root_path" value="" checked>
</div> <span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<div class="modal fade" id="dir_upload_select" tabindex="-1" role="dialog" <i class="far fa-folder"></i>
aria-labelledby="dir_select" aria-hidden="true"> <i class="far fa-folder-open"></i>
<div class="modal-dialog" role="document"> {{ translate('serverFiles', 'files', data['lang']) }}
<div class="modal-content"> </span>
<div class="modal-header"> </input>
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard', </div>
'selectZipDir', data['lang']) }}</h5> </div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <div class="modal-footer">
<span aria-hidden="true">&times;</span> <button type="button" class="btn btn-secondary" data-dismiss="modal">{{
</button> translate('serverWizard', 'close', data['lang']) }}</button>
</div> <button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
<div class="modal-body"> translate('serverWizard', 'save', data['lang']) }}</button>
<div class="tree-ctx-item" id="main-tree-div-upload" data-path="" </div>
style="overflow: scroll; max-height:75%;"> </div>
<input type="radio" id="main-tree-input-upload" name="root_path" value="" checked> </div>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""> </div>
<i class="far fa-folder"></i> <button id="upload_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
<i class="far fa-folder-open"></i> }}</button>
{{ translate('serverFiles', 'files', data['lang']) }} <button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
</span> data['lang'])
</input> }}</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverWizard', 'close', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
translate('serverWizard', 'save', data['lang']) }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
<button id="upload_submit" type="submit" title="You must select server root dir first" disabled
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button>
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
data['lang'])
}}</button>
</div>
</div> </div>
</form> </form>
</p>
</div> </div>
</div> </div>
</div> </div>
@ -494,6 +419,15 @@
font-size: 10px; font-size: 10px;
vertical-align: top; vertical-align: top;
} }
div>.input-group>.custom-file-input {
position: relative !important;
-webkit-box-flex: 1 !important;
-ms-flex: 1 1 auto !important;
flex: 1 1 auto !important;
width: 1% !important;
margin-bottom: 0 !important;
border: 1px solid var(--outline);
}
.scroll { .scroll {
max-height: 12em; max-height: 12em;
@ -583,7 +517,7 @@
var file; var file;
function sendFile() { function sendFile() {
file = $("#file")[0].files[0] file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div id="upload-progress-bar" 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>' document.getElementById("upload_input").innerHTML = '<div class="progress" style="width: 100%;"><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 xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf") let token = getCookie("_xsrf")
let fileName = encodeURIComponent(file.name) let fileName = encodeURIComponent(file.name)
@ -611,7 +545,7 @@
xmlHttpRequest.addEventListener('load', (event) => { xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') { if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!') console.log('Upload for file', file.name, 'was successful!')
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>'; document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
document.getElementById("lower_half").style.visibility = "visible"; document.getElementById("lower_half").style.visibility = "visible";
} }
else { else {
@ -864,5 +798,15 @@
}); });
} }
$('#file').change(function () {
console.log("File changed");
if ($('#file').val()) {
$('#upload-button').prop("disabled", false);
document.getElementById("fileLabel").innerHTML = $('#file').val().split('\\').pop().split('/').pop();
console.log("File changed good");
}
});
</script> </script>
{% end %} {% end %}

View File

@ -39,7 +39,8 @@
<div class="form-group"> <div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang']) <label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang'])
}}</label></br> }}</label></br>
<select style="width: 80%" required class="selectpicker form-control form-control-lg select-css" <div class="input-group">
<select data-container="body" required class="selectpicker form-control form-control-lg select-css"
id="steam_server" data-live-search="true" name="steam_server"> id="steam_server" data-live-search="true" name="steam_server">
<option value="None">None</option> <option value="None">None</option>
{% for s in data['servers'] %} {% for s in data['servers'] %}
@ -50,10 +51,13 @@
{% end %} {% end %}
{% end %} {% end %}
</select> </select>
{% if data['super_user'] %} {% if data['super_user'] %}
&nbsp;&nbsp;<i onclick="refreshCache()" style="float: left;" id="refresh-cache" <div class="input-group-append">
class="refresh-class fas fa-sync"></i> <button class="btn custom-picker" type="button" onclick="refreshCache()"><i id="refresh-cache" class="refresh-class fas fa-sync"></i></button>
</div>
{% end %} {% end %}
</div>
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">

View File

@ -31,143 +31,179 @@
</ul> </ul>
<div class="d-none" id="overlay" onclick="hide(event)"></div> <div class="d-none" id="overlay" onclick="hide(event)"></div>
<div class="row"> <div class="row">
{% if data['online'] %}
<div class="col-sm-6 grid-margin stretch-card"> <div class="col-sm-6 grid-margin stretch-card">
<div class="card"> <div class="card" id="creation_wizard">
<div class="card-body"> <div class="card-body">
{% if data["server_api"] and data["online"] %}
<a href="https://serverjars.com/" target="_blank" alt="serverjars icon"><img src="../../static/assets/images/serverjars/ICON.svg"
style="float: right; width: 40px; position: relative;"></a>
{% end %}
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4> <h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
<br /> <br />
<p class="card-description"> <p class="card-description">
<form method="post" class="server-wizard" onSubmit="wait_msg()"> <form method="post" class="server-wizard" onSubmit="wait_msg()">
{% raw xsrf_form_html() %} {% if data["server_api"] and data["online"] %}
<div class="row"> <fieldset>
<div class="col-sm-12"> {% else %}
<div class="form-group"> <fieldset disabled="disabled">
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang']) <style>
}}</label> #creation_wizard {
{% if data['super_user'] %} -webkit-filter: grayscale(1);
<select style="width: 90%;" required class="form-control form-control-lg select-css" id="server_jar" }
name="server_jar" onchange="serverJarChange(this)">
{% else %}
<select required class="form-control form-control-lg select-css" id="server_jar" name="server_jar"
onchange="serverJarChange(this)">
{% end %}
<option value="None">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
{% for s in data['server_types'] %}
<option value="{{ s }}">{{ s.capitalize() }}</option>
{% end %}
</select>
{% if data['super_user'] %}
&nbsp;&nbsp;<i onclick="refreshCache()" id="refresh-cache" class="refresh-class fas fa-sync"></i>
{% end %}
</div>
</div>
<div class="col-sm-12"> .api-alert {
<div class="form-group"> position: absolute;
<label for="server_type">{{ translate('serverWizard', 'serverSelect', data['lang']) }}</label> top: -5px;
<select required class="form-control form-control-lg select-css" id="server_type" name="server_type" left: 0;
onchange="serverTypeChange(this)"> font-size: 50px !important;
<option value="">{{ translate('serverWizard', 'selectServer', data['lang']) }}</option> color: #fff;
</select> background: rgb(0, 170, 170);
</div> opacity: .4;
</div> width: 100%;
height: 100%;
z-index: 100;
}
<div class="col-sm-12"> .api-alert p {
<div class="form-group"> margin: 0;
<label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label> position: absolute;
<select required class="form-control form-control-lg select-css" id="server" name="server"> top: 50%;
<option value="">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option> left: 50%;
</select> text-align: center;
</div> font-size: 20px;
</div> -ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
<div class="col-sm-12"> }
<div class="form-group"> </style>
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label> {% end %}
<input type="text" class="form-control" id="server_name" name="server_name" {% raw xsrf_form_html() %}
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required> <div class="row">
</div> <div class="col-sm-12">
</div> <div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang'])
</div> }}</label>
<br /> <div class="input-group">
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small <select required class="form-control form-control-lg select-css" id="server_jar" name="server_jar" onchange="serverJarChange(this)">
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', <option value="None">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
data['lang']) }}</small></h4> {% for s in data['server_types'] %}
<hr> <option value="{{ s }}">{{ s.capitalize() }}</option>
<div class="row"> {% end %}
</select>
<div class="col-sm-4"> {% if data['super_user'] %}
<div class="form-group"> <div class="input-group-append">
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ <button class="btn custom-picker" type="button" onclick="refreshCache()"><i id="refresh-cache" class="refresh-class fas fa-sync"></i></button>
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> </div>
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" {% end %}
min="0.5" required>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5"
min="0.5" required>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1"
max="65535 " required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<div id="accordion-1">
<div class="card">
<div class="card-header p-2" id="Role-1">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true"
aria-controls="collapseRole-1">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
</p>
</div> </div>
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent=""> </div>
<div class="card-body scroll"> </div>
<div class="form-group">
{% for r in data['roles'] %} <div class="col-sm-12">
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" <div class="form-group">
type="checkbox">&nbsp; <label for="server_type">{{ translate('serverWizard', 'serverSelect', data['lang']) }}</label>
{{ r['role_name'].capitalize() }}</label></span> <select required class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)">
{% end %} <option value="">{{ translate('serverWizard', 'selectServer', data['lang']) }}</option>
</select>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label>
<select required class="form-control form-control-lg select-css" id="server" name="server">
<option value="">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option>
</select>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
</div>
<br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4>
<hr>
<div class="row">
<div class="col-sm-4">
<div class="form-group">
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" min="0.5" required>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5" min="0.5" required>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1" max="65535 " required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<div id="accordion-1">
<div class="card">
<div class="card-header p-2" id="Role-1">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" aria-controls="collapseRole-1">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
</p>
</div>
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp; {{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'buildServer',
data['lang']) }}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
}}</button>
</fieldset>
</div> </div>
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'buildServer', </fieldset>
data['lang']) }}</button> {% if not data["server_api"] and data["online"] %}
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) <div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
}}</button> <p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: red;"></i>&nbsp;{{ translate('error', 'serverJars1', data['lang']) }}<a style="color: red;"; href="https://status.craftycontrol.com/status/craftycontrol"
target="_blank">&nbsp;{{ translate('error', 'craftyStatus', data['lang']) }}</a>
</form> &nbsp;{{ translate('error', 'serverJars2', data['lang']) }}</p></div>
</p> {% end %}
{% if not data["online"] %}
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
<p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: red;"></i>&nbsp;{{ translate('error', 'noInternet', data['lang']) }}</p></div>
{% end %}
</div> </div>
</div> </form>
{% end %}
</div> </div>
<div class="col-sm-6 grid-margin stretch-card"> <!-- Import an Existing Server -->
<div class="col-md-6 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -183,8 +219,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label> <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value="" <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div> </div>
</div> </div>
@ -192,16 +227,14 @@
<div class="form-group"> <div class="form-group">
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{ <label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{
translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label> translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path" <input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server" required>
placeholder="/var/opt/server" required>
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label> <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
placeholder="paper.jar" required>
</div> </div>
</div> </div>
@ -209,8 +242,7 @@
</div> </div>
<br /> <br />
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4> data['lang']) }}</small></h4>
<hr> <hr>
<div class="row"> <div class="row">
@ -219,8 +251,7 @@
<div class="form-group"> <div class="form-group">
<label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ <label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory2" name="min_memory" value="1" step="0.5" <input type="number" class="form-control" id="min_memory2" name="min_memory" value="1" step="0.5" min="0.5" required>
min="0.5" required>
</div> </div>
</div> </div>
@ -228,8 +259,7 @@
<div class="form-group"> <div class="form-group">
<label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ <label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory2" name="max_memory" value="2" step="0.5" <input type="number" class="form-control" id="max_memory2" name="max_memory" value="2" step="0.5" min="0.5" required>
min="0.5" required>
</div> </div>
</div> </div>
@ -237,8 +267,7 @@
<div class="form-group"> <div class="form-group">
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ <label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label> translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1" <input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1" max="65535" required>
max="65535" required>
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
@ -246,8 +275,7 @@
<div id="accordion-2"> <div id="accordion-2">
<div class="card"> <div class="card">
<div class="card-header p-2" id="Role-2"> <div class="card-header p-2" id="Role-2">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
aria-controls="collapseRole-2">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }}
<small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small> data['lang']) }}</small>
@ -257,8 +285,7 @@
<div class="card-body scroll"> <div class="card-body scroll">
<div class="form-group"> <div class="form-group">
{% for r in data['roles'] %} {% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span> {{ r['role_name'].capitalize() }}</label></span>
{% end %} {% end %}
</div> </div>
@ -279,9 +306,9 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="row"> <!-- Import from a Zip File -->
<div class="col-sm-6 grid-margin stretch-card"> <div class="col-md-6 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -298,8 +325,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label> <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value="" <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div> </div>
</div> </div>
@ -307,8 +333,7 @@
<div class="form-group"> <div class="form-group">
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{ <label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{
translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label> translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
<input type="text" class="form-control" id="server_path" name="server_path" <input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip" required>
placeholder="/var/opt/server.zip" required>
</div> </div>
</div> </div>
@ -326,8 +351,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label> <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
placeholder="paper.jar" required>
</div> </div>
</div> </div>
</div> </div>
@ -336,36 +360,31 @@
<div class="col-sm-12"> <div class="col-sm-12">
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4> data['lang']) }}</small></h4>
<hr> <hr>
<div class="row"> <div class="row">
<div class="col-sm-4">
<div class="col-sm-12">
<div class="form-group"> <div class="form-group">
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ <label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" step="0.5" <input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" step="0.5" min="0.5" required>
min="0.5" required>
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-4">
<div class="form-group"> <div class="form-group">
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ <label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" step="0.5" <input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" step="0.5" min="0.5" required>
min="0.5" required>
</div> </div>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-4">
<div class="form-group"> <div class="form-group">
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ <label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label> translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1" <input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1" max="65535" required>
max="65535" required>
</div> </div>
</div> </div>
@ -374,8 +393,7 @@
<div id="accordion-3"> <div id="accordion-3">
<div class="card"> <div class="card">
<div class="card-header p-2" id="Role-3"> <div class="card-header p-2" id="Role-3">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" <p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) <i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang'])
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small> data['lang']) }}</small>
@ -385,8 +403,7 @@
<div class="card-body scroll"> <div class="card-body scroll">
<div class="form-group"> <div class="form-group">
{% for r in data['roles'] %} {% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span> {{ r['role_name'].capitalize() }}</label></span>
{% end %} {% end %}
</div> </div>
@ -396,13 +413,12 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-12" style="visibility: hidden;"> <div class="col-sm-12" style="visibility: hidden;" hidden>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path"> <input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
</div> </div>
</div> </div>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" <div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -413,8 +429,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path="" <div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input" name="root_path" value="" checked> <input type="radio" id="main-tree-input" name="root_path" value="" checked>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""> <span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i> <i class="far fa-folder"></i>
@ -434,8 +449,7 @@
</div> </div>
</div> </div>
</div> </div>
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled <button id="zip_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button> }}</button>
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm', <button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
data['lang']) data['lang'])
@ -446,7 +460,8 @@
</p> </p>
</div> </div>
</div> </div>
<div class="col-sm-6 grid-margin stretch-card"> <!-- Upload Zip File For Server Import -->
<div class="col-md-6 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -458,157 +473,125 @@
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type"> <input type="hidden" value="import_zip" name="create_type">
<div class="row"> <div class="form-group">
<div class="col-sm-12"> <label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<div class="col-sm-12"> <input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
<div class="form-group"> </div>
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label> <div class="form-group">
<input type="text" class="form-control" id="server_name" name="server_name" value="" <label for="server">{{ translate('serverWizard', 'serverUpload', data['lang']) }}</label>
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required> <div id="upload_input" class="input-group">
</div> <div class="custom-file">
<input type="file" multiple="false" class="custom-file-input" id="file" name="file" required>
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('serverWizard', 'labelZipFile', data['lang']) }}</label>
</div> </div>
<div class="input-group-append">
<div class="col-sm-12"> <button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()" disabled>{{ translate('serverWizard',
<div class="form-group"> 'uploadButton', data['lang']) }}</button>
<label for="server">{{ translate('serverWizard', 'serverUpload', 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" onclick="sendFile()">{{ translate('serverWizard',
'uploadButton', data['lang']) }}</button>
</span>
</div>
</div> </div>
</div> </div>
<div id="lower_half" style="visibility: hidden;"> </div>
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{
translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_upload_button" type="button">{{
translate('serverWizard', 'clickRoot', data['lang']) }}</button>
</div>
</div>
<div id="lower_half" style="visibility: hidden;" hidden>
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{
translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_upload_button" type="button">{{
translate('serverWizard', 'clickRoot', data['lang']) }}</button>
</div>
<div class="col-sm-12"> <div class="form-group">
<div class="form-group"> <label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label> <input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar" required>
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" </div>
placeholder="paper.jar" required>
</div>
</div>
<div class="col-sm-12"> <h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small data['lang']) }}</small></h4>
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', <hr>
data['lang']) }}</small></h4>
<hr>
<div class="row">
<div class="col-sm-12"> <div class="form-group">
<div class="form-group"> <label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> <input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" step="0.5" min="0.5" required>
<input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" </div>
step="0.5" min="0.5" required>
</div> <div class="form-group">
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" step="0.5" min="0.5" required>
</div>
<div class="form-group">
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1" max="65535" required>
</div>
<div class="form-group">
<div id="accordion-3">
<div class="card">
<div class="card-header p-2" id="Role-3">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole',
data['lang'])
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
</p>
</div> </div>
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
<div class="col-sm-12"> <div class="card-body scroll">
<div class="form-group"> <div class="form-group">
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ {% for r in data['roles'] %}
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label> <span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">&nbsp;
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" {{ r['role_name'].capitalize() }}</label></span>
step="0.5" min="0.5" required> {% end %}
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1"
max="65535" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<div id="accordion-3">
<div class="card">
<div class="card-header p-2" id="Role-3">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3"
aria-expanded="true" aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole',
data['lang'])
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
</p>
</div>
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-12" style="visibility: hidden;">
<div class="form-group">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
</div>
</div>
<div class="modal fade" id="dir_upload_select" tabindex="-1" role="dialog"
aria-labelledby="dir_select" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard',
'selectZipDir', data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div-upload" data-path=""
style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input-upload" name="root_path" value="" checked>
<span id="main-tree-upload" class="files-tree-title tree-caret-down root-dir"
data-path="">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{{ translate('serverFiles', 'files', data['lang']) }}
</span>
</input>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverWizard', 'close', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
translate('serverWizard', 'save', data['lang']) }}</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<button id="upload_submit" type="submit" title="You must select server root dir first" disabled
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button>
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
data['lang'])
}}</button>
</div> </div>
<div style="visibility: hidden;">
<div class="form-group">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
</div>
</div>
<div class="modal fade" id="dir_upload_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard',
'selectZipDir', data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div-upload" data-path="" style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input-upload" name="root_path" value="" checked>
<span id="main-tree-upload" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{{ translate('serverFiles', 'files', data['lang']) }}
</span>
</input>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverWizard', 'close', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
translate('serverWizard', 'save', data['lang']) }}</button>
</div>
</div>
</div>
</div>
</div>
<button id="upload_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button>
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
data['lang'])
}}</button>
</div> </div>
</form> </form>
</p> </p>
@ -616,7 +599,12 @@
</div> </div>
</div> </div>
</div> </div>
</div> <style>
button>i.refresh-class {
margin: 0px;
}
</style>
<style> <style>
.steam::after { .steam::after {
content: 'BETA'; content: 'BETA';
@ -792,6 +780,7 @@
{% end %} {% end %}
{% block js%} {% block js%}
<script> <script>
document.getElementById("root_files_button").addEventListener("click", function () { document.getElementById("root_files_button").addEventListener("click", function () {
if (document.forms["zip"]["server_path"].value != "") { if (document.forms["zip"]["server_path"].value != "") {
@ -816,7 +805,6 @@
bootbox.alert("You must input a path before selecting this button"); bootbox.alert("You must input a path before selecting this button");
} }
</script> </script>
{% end %} {% end %}
{% block js %} {% block js %}
@ -825,7 +813,7 @@
var file; var file;
function sendFile() { function sendFile() {
file = $("#file")[0].files[0] file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div id="upload-progress-bar" 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>' document.getElementById("upload_input").innerHTML = '<div class="progress" style="width: 100%;"><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 xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf") let token = getCookie("_xsrf")
let fileName = file.name let fileName = file.name
@ -853,8 +841,9 @@
xmlHttpRequest.addEventListener('load', (event) => { xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') { if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!') console.log('Upload for file', file.name, 'was successful!')
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>'; document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
document.getElementById("lower_half").style.visibility = "visible"; document.getElementById("lower_half").style.visibility = "visible";
document.getElementById("lower_half").hidden = false;
} }
else { else {
let response_text = JSON.parse(event.target.responseText); let response_text = JSON.parse(event.target.responseText);
@ -972,12 +961,13 @@
function wait_msg(importing) { function wait_msg(importing) {
bootbox.alert({ bootbox.alert({
title: importing ? '{% raw translate("serverWizard", "importing", data["lang"]) %}' : '{% raw translate("serverWizard", "downloading", data["lang"]) %}', title: importing ? '{% raw translate("serverWizard", "importing", data["lang"]) %}' : '{% raw translate("serverWizard", "downloading", data["lang"]) %}',
message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}', message: importing ? '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}': '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}<br><br><a href="https://serverjars.com" target="_blank" style="text-align: center;"><img src="../../static/assets/images/serverjars/FULL-WHITE.svg" alt="Powered by serverjars.com" width="40%"></a>',
}); });
} }
function show_file_tree() { function show_file_tree() {
if (upload) { if (upload) {
console.log("upload true")
$("#dir_upload_select").modal(); $("#dir_upload_select").modal();
} else { } else {
$("#dir_select").modal(); $("#dir_select").modal();
@ -1133,6 +1123,15 @@
}); });
} }
$('#file').change(function () {
console.log("File changed");
if ($('#file').val()) {
$('#upload-button').prop("disabled", false);
document.getElementById("fileLabel").innerHTML = $('#file').val().split('\\').pop().split('/').pop();
console.log("File changed good");
}
});
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
var text = '{% raw data["js_server_types"] %}'; var text = '{% raw data["js_server_types"] %}';

View File

@ -186,7 +186,13 @@
"terribleFailure": "What a Terrible Failure!", "terribleFailure": "What a Terrible Failure!",
"superError": "You must be a super user to complete this action.", "superError": "You must be a super user to complete this action.",
"fileError": "File type must be an image.", "fileError": "File type must be an image.",
"migration": "Crafty's main server storage is being mirgated to a new location. All server starts have been suspended during this time. Please wait while we finish this migration" "migration": "Crafty's main server storage is being mirgated to a new location. All server starts have been suspended during this time. Please wait while we finish this migration",
"serverJars1": "Server JARs API unreachable. Please check",
"bedrockError": "Bedrock downloads unavailable. Please check",
"craftyStatus": "Crafty's status page",
"serverJars2": "for the most up to date information.",
"cronFormat": "Invalid Cron format detected",
"noInternet": "Crafty is having trouble accessing the internet. Server Creation has been disabled. Please check your internet connection and refresh this page."
}, },
"footer": { "footer": {
"allRightsReserved": "All rights reserved", "allRightsReserved": "All rights reserved",
@ -197,7 +203,8 @@
"forgotPassword": "Forgot Password", "forgotPassword": "Forgot Password",
"login": "Log In", "login": "Log In",
"password": "Password", "password": "Password",
"username": "Username" "username": "Username",
"viewStatus": "View Public Status Page"
}, },
"notify": { "notify": {
"activityLog": "Activity Logs", "activityLog": "Activity Logs",
@ -209,6 +216,10 @@
"preparingLogs": " Please wait while we prepare your logs... We`ll send a notification when they`re ready. This may take a while for large deployments.", "preparingLogs": " Please wait while we prepare your logs... We`ll send a notification when they`re ready. This may take a while for large deployments.",
"supportLogs": "Support Logs" "supportLogs": "Support Logs"
}, },
"offline": {
"offline": "Offline",
"pleaseConnect": "Please connect to the internet to use Crafty."
},
"panelConfig": { "panelConfig": {
"adminControls": "Admin Controls", "adminControls": "Admin Controls",
"allowedServers": "Allowed Servers", "allowedServers": "Allowed Servers",
@ -243,6 +254,7 @@
"customLogin": { "customLogin": {
"customLoginPage": "Customise the Login Page", "customLoginPage": "Customise the Login Page",
"loginImage": "Upload a background image for the login screen.", "loginImage": "Upload a background image for the login screen.",
"labelLoginImage": "Choose your Login Background",
"backgroundUpload": "Background Upload", "backgroundUpload": "Background Upload",
"loginBackground": "Login Background Image", "loginBackground": "Login Background Image",
"loginOpacity": "Select the Login Window Opacity", "loginOpacity": "Select the Login Window Opacity",
@ -539,6 +551,7 @@
"importServerButton": "Import Server!", "importServerButton": "Import Server!",
"importZip": "Import from a Zip File", "importZip": "Import from a Zip File",
"uploadZip": "Upload Zip File For Server Import", "uploadZip": "Upload Zip File For Server Import",
"labelZipFile": "Choose your Zip File",
"maxMem": "Maximum Memory", "maxMem": "Maximum Memory",
"minMem": "Minimum Memory", "minMem": "Minimum Memory",
"myNewServer": "My New Server", "myNewServer": "My New Server",
@ -572,7 +585,8 @@
"documentation": "Documentation", "documentation": "Documentation",
"navigation": "Navigation", "navigation": "Navigation",
"newServer": "Create New Server", "newServer": "Create New Server",
"servers": "Servers" "servers": "Servers",
"inApp": "In App Docs"
}, },
"userConfig": { "userConfig": {
"apiKey": "API Keys", "apiKey": "API Keys",
@ -614,4 +628,4 @@
"manager": "Manager", "manager": "Manager",
"selectManager": "Select Manager for User" "selectManager": "Select Manager for User"
} }
} }

View File

@ -12,23 +12,23 @@
}, },
"apiKeys": { "apiKeys": {
"apiKeys": "Claves API", "apiKeys": "Claves API",
"auth": "Autorizado? ", "auth": "¿Autorizado? ",
"buttons": "Botones", "buttons": "Botones",
"config": "Configuración", "config": "Configuración",
"crafty": "Crafty: ", "crafty": "Crafty: ",
"created": "Creado", "created": "Creado",
"createNew": "Crear un nuevo API Token", "createNew": "Crear un nuevo Token de API",
"deleteKeyConfirmation": "¿Quieres eliminar esta clave de API? Esto no se puede deshacer.", "deleteKeyConfirmation": "¿Quieres eliminar esta clave de API? Esto no se puede deshacer.",
"deleteKeyConfirmationTitle": "¿Eliminar la clave API ${keyId}?", "deleteKeyConfirmationTitle": "¿Eliminar la clave API ${keyId}?",
"getToken": "Conseguir un Token", "getToken": "Conseguir un Token",
"name": "Nombre", "name": "Nombre",
"nameDesc": "Como te gustaria llamar a este API token? ", "nameDesc": "¿Como te gustaría llamar a este Token de API? ",
"no": "No", "no": "No",
"pageTitle": "Editar las Claves API", "pageTitle": "Editar las Claves API",
"permName": "Nombre del Permiso", "permName": "Nombre del Permiso",
"perms": "Permisos", "perms": "Permisos",
"server": "Servidor: ", "server": "Servidor: ",
"superUser": "Super User", "superUser": "Super Usuario",
"yes": "Si" "yes": "Si"
}, },
"base": { "base": {
@ -39,81 +39,82 @@
"hugeDesc": "Un enorme", "hugeDesc": "Un enorme",
"pageDescription": "Sin esta gente, Crafty no existiría", "pageDescription": "Sin esta gente, Crafty no existiría",
"pageTitle": "Créditos", "pageTitle": "Créditos",
"patreonDesc": "Patreon / Ko-fi supporters!", "patreonDesc": "A nuestros Patreon / Ko-fi Supporters",
"patreonOther": "Otro", "patreonOther": "Otro",
"patreonSupporter": "A nuestros Patreon / Ko-fi Supporters", "patreonSupporter": "Patreon / Ko-fi supporters!",
"patreonUpdate": "Última Actualización:", "patreonUpdate": "Última Actualización:",
"retiredStaff": "Staff retirado", "retiredStaff": "Staff retirado",
"subscriberName": "Nombre", "subscriberName": "Nombre",
"subscriptionLevel": "Nivel", "subscriptionLevel": "Nivel",
"supportTeam": "Equipo de soporte y documentación", "supportTeam": "Equipo de soporte y documentación",
"thankYou": "GRACIAS", "thankYou": "GRACIAS",
"translationDesc": "a nuestra comunidad, que tradujo!", "translationDesc": "a nuestra comunidad que traduce! [ Activo = 🟢 Inactivo/Retirado = ⚪ ]",
"translationName": "Nombre", "translationName": "Nombre",
"translationTitle": "Traducciones de Idiomas", "translationTitle": "Traduccion de Idiomas",
"translator": "Traductores" "translator": "Traductores"
}, },
"dashboard": { "dashboard": {
"actions": "Acciones", "actions": "Acciones",
"allServers": "Todos los servidores", "allServers": "Todos los servidores",
"avg": "Avg", "avg": "Avg",
"backups": "Backups", "backups": "Respaldos",
"bePatientClone": "Espere mientras clonamos el servidor.<br /> Esta pantalla se actualizará en un momento", "bePatientClone": "Espere mientras clonamos el servidor.<br /> Esta pantalla se actualizará en un momento",
"bePatientRestart": "Espere mientras reiniciamos el servidor.<br /> Esta pantalla se actualizará en un momento", "bePatientRestart": "Espere mientras reiniciamos el servidor.<br /> Esta pantalla se actualizará en un momento",
"bePatientStart": "Espere mientras iniciamos el servidor.<br /> Esta pantalla se actualizará en un momento", "bePatientStart": "Espere mientras iniciamos el servidor.<br /> Esta pantalla se actualizará en un momento",
"bePatientStop": "Espere mientras detenemos el servidor.<br /> Esta pantalla se actualizará en un momento", "bePatientStop": "Espere mientras detenemos el servidor.<br /> Esta pantalla se actualizará en un momento",
"cannotSee": "¿No puedes verlo todo?", "cannotSee": "¿No se muestra correctamente?",
"cannotSeeOnMobile": "¿No puedes verlo todo en móvil?", "cannotSeeOnMobile": "¿No se muestra correctamente en móvil?",
"cannotSeeOnMobile2": "Intenta desplazar la tabla desde los lados..", "cannotSeeOnMobile2": "Intente girar el dispositivo hacia los lados.",
"clone": "Clonar", "clone": "Clonar",
"cloneConfirm": "¿Está seguro de que desea clonar este servidor? Este proceso puede llevar un tiempo", "cloneConfirm": "¿Está seguro de que desea clonar este servidor? Este proceso puede llevar un tiempo",
"cpuCores": "Nucleos de CPU", "cpuCores": "Núcleos de CPU",
"cpuCurFreq": "Reloj de CPU Actual", "cpuCurFreq": "Reloj de CPU Actual",
"cpuMaxFreq": "Reloj de CPU Maximo", "cpuMaxFreq": "Reloj de CPU Máximo",
"cpuUsage": "Uso de CPU", "cpuUsage": "Uso de CPU",
"crashed": "Crashed", "crashed": "Crasheado",
"dashboard": "Panel de control", "dashboard": "Panel de control",
"delay-explained": "El agente/servicio ha iniciado recientemente y está retrasando el inicio de la instancia del servidor de Minecraft.", "delay-explained": "El agente/servicio inicio recientemente y está retrasando el inicio de la instancia del servidor de Minecraft.",
"host": "Host", "host": "Host",
"kill": "Matar Proceso", "kill": "Matar Proceso",
"killing": "Matando el proceso...", "killing": "Cerrando el proceso...",
"lastBackup": "Último:", "lastBackup": "Último:",
"max": "Max", "max": "Máx",
"memUsage": "Uso de memoria", "memUsage": "Uso de memoria",
"motd": "MOTD", "motd": "MOTD",
"newServer": "Crear nuevo Servidor", "newServer": "Crear nuevo Servidor",
"nextBackup": "Próximo:", "nextBackup": "Próximo:",
"no-servers": "Actualmente no hay servidores. Para comenzar, haga click", "no-servers": "Actualmente no hay servidores. Para comenzar, haga click en",
"offline": "Desconectado", "offline": "Desconectado",
"online": "En línea", "online": "En línea",
"players": "Jugadores", "players": "Jugadores",
"restart": "Reiniciar", "restart": "Reiniciar",
"sendingCommand": "Enviando tu comando", "sendingCommand": "Enviando tu comando",
"server": "Server", "server": "Servidor",
"servers": "Servers", "servers": "Servidores",
"size": "Tamaño del directorio del servidor", "size": "Tamaño del directorio del servidor",
"start": "Iniciar", "start": "Iniciar",
"starting": "Inicio-retrasado", "starting": "Inicio-retrasado",
"status": "Estado", "status": "Estado",
"stop": "Detener", "stop": "Detener",
"version": "Versión", "version": "Versión",
"welcome": "Bienvenido a Crafty Controller" "welcome": "Bienvenido a Crafty Controller",
"installing": "Instalando..."
}, },
"datatables": { "datatables": {
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": activar para ordenar las columnas de masnera ascendente", "sortAscending": ": activar para ordenar las columnas de manera ascendente",
"sortDescending": ": activar para ordenar las columnas de masnera descendente" "sortDescending": ": activar para ordenar las columnas de manera descendente"
}, },
"buttons": { "buttons": {
"collection": "Colección <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'/>", "collection": "Colección <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'/>",
"colvis": "Visibilidad de las columnas", "colvis": "Visibilidad de las columnas",
"colvisRestore": "Restaurar visibilidad", "colvisRestore": "Restaurar visibilidad",
"copy": "Copiar", "copy": "Copiar",
"copyKeys": "Presiona ctrl o u2318 + C para copiar los datos de la tabla al portapapeles de tu sistema.<br><br>Para cancelar, click en este mensaje o presiona escape.", "copyKeys": "Presiona ctrl o u2318 + C para copiar los datos de la tabla al portapapeles de tu sistema.<br><br>Para cancelar, cliquea este mensaje o presiona escape.",
"copySuccess": { "copySuccess": {
"1": "Copiado 1 fila al portapapeles", "1": "Copiado 1 fila al portapapeles",
"_": "Copiadas %d filas al portapapeles" "_": "Copiado %d filas al portapapeles"
}, },
"copyTitle": "Copiar al Portapapeles", "copyTitle": "Copiar al Portapapeles",
"csv": "CSV", "csv": "CSV",
@ -128,15 +129,15 @@
}, },
"decimal": "", "decimal": "",
"emptyTable": "No hay datos disponibles en la tabla", "emptyTable": "No hay datos disponibles en la tabla",
"info": "Mostrando _START_ a _END_ de _TOTAL_ entradas", "info": "Mostrando _START_ hasta _END_ de _TOTAL_ entradas",
"infoEmpty": "Mostrando 0 a 0 de 0 entradas", "infoEmpty": "Mostrando 0 de 0 entradas",
"infoFiltered": "(filtrado de _MAX_ entradas totales)", "infoFiltered": "(filtrado de _MAX_ entradas totales)",
"infoPostFix": "", "infoPostFix": "",
"lengthMenu": "Mostrar _MENU_ entradas", "lengthMenu": "Mostrar entradas de _MENU_",
"loadingRecords": "Cargando...", "loadingRecords": "Cargando...",
"paginate": { "paginate": {
"first": "Primero", "first": "Primero",
"last": "Ultimo", "last": "Último",
"next": "Siguiente", "next": "Siguiente",
"previous": "Anterior" "previous": "Anterior"
}, },
@ -160,27 +161,39 @@
} }
}, },
"thousands": ",", "thousands": ",",
"zeroRecords": "No se encontraron registros coincidentes" "zeroRecords": "No se encontraron registros que coincidan"
} }
}, },
"error": { "error": {
"closedPort": "Hemos detectado que el puerto {} podría no estar abierto en la red del host o un firewall lo está bloqueando. Las conexiones remotas de clientes al servidor podrían estar limitadas.",
"contact": "Contacta el soporte de Crafty Control desde Discord", "contact": "Contacta el soporte de Crafty Control desde Discord",
"embarassing": "Oh por, bueno, esto es vergonzoso.", "embarassing": "¡Oh cielos! Esto es embarazoso.",
"error": "Error!", "error": "Error!",
"eulaAgree": "Estás de acuerdo?", "eulaAgree": "¿Estás de acuerdo?",
"eulaMsg": "Debes aceptar el EULA. Una copia del EULA de Minecraft esta vinculada debajo de este mensaje.", "eulaMsg": "Debes aceptar el ",
"privMsg": "y el ",
"eulaTitle": "Aceptar EULA", "eulaTitle": "Aceptar EULA",
"fileTooLarge": "Subida fallida. Carga de archivo demasiado grande. Póngase en contacto con el administrador del sistema para obtener ayuda.", "agree": "Aceptar",
"cancel": "Cancelar",
"fileTooLarge": "Subida fallida. El archivo es demasiado grande. Contacte al administrador del sistema para obtener asistencia.",
"hereIsTheError": "Aquí está el error.", "hereIsTheError": "Aquí está el error.",
"internet": "Hemos detectado que la maquina ejecutando Crafty no tiene acceso a internet. Las conexiones de clientes al servidor podrían estar limitadas.", "internet": "Hemos detectado que la máquina ejecutando Crafty no tiene acceso a internet. Las conexiones de cliente al servidor podrían ser limitadas.",
"no-file": "No se puede localizar el archivo solicitado. Verifique dos veces la ruta. Capaz Crafty no tiene los permisos adecuados?", "no-file": "No se puede encontrar el archivo solicitado. Verifique la dirección. ¿Quizas Crafty no tiene los permisos adecuados?",
"noJava": "Server {} fallo al iniciar con código de error: Detectamos que Java no esta instalado, Por favor instale java y inicie el servidor.", "noJava": "Servidor {} no pudo iniciarse: Detectamos que Java no está instalado, Por favor instale java e inicie el servidor.",
"not-downloaded": "Parece que no podemos encontrar su archivo ejecutable. Capaz no ha terminado de descargarse o los permisos están configurados como ejecutables.", "installerJava": "Fallo al instalar {} : La instalación de Forge requiere Java. Detectamos que Java no está instalado. Por favor instale java e inicie el servidor.",
"portReminder": "Detectamos que es la primera vez que se inicia {}. Asegúrese de configurar el puerto {} A través de su router/firewall para hacer el servidor accesible.", "not-downloaded": "No podemos encontrar el archivo ejecutable. ¿Ha terminado de descargarse? ¿Están los permisos puestos como ejecutable?",
"portReminder": "Detectamos que es la primera vez que se inicia {}. Asegúrese de configurar el puerto {} a través de su router/firewall para hacer el servidor accesible por Internet.",
"start-error": "Servidor {} fallo al iniciar con código de error: {}", "start-error": "Servidor {} fallo al iniciar con código de error: {}",
"terribleFailure": "¡Un terrible error!" "terribleFailure": "¡Un terrible error!",
}, "superError": "Debes ser un super usuario para completar esta acción.",
"fileError": "El tipo de archivo debe ser una imagen.",
"migration": "El almacenamiento del servidor principal de Crafty se está migrando a una nueva ubicación. El inicio de servidores se han suspendido durante este periodo. Espere mientras finalizamos esta migración.",
"serverJars1": "API de Servidor JAR no disponible. por favor, compruebe",
"bedrockError": "Descargas de Bedrock no disponibles. por favor, compruebe",
"craftyStatus": "Página de estados de Crafty",
"serverJars2": "para la información más actualizada.",
"cronFormat": "Detectado formato CRON invalido",
"noInternet": "Crafty tiene problemas para acceder a Internet. La creación del servidor ha sido deshabilitada. Verifique su conexión a Internet y actualice esta página.Crafty tiene problemas para acceder a Internet. La creación del servidor ha sido deshabilitada. Verifique su conexión a Internet y actualice esta página."
},
"footer": { "footer": {
"allRightsReserved": "Todos los derechos reservados", "allRightsReserved": "Todos los derechos reservados",
"copyright": "Copyright", "copyright": "Copyright",
@ -194,97 +207,125 @@
}, },
"notify": { "notify": {
"activityLog": "Registros de actividad", "activityLog": "Registros de actividad",
"backupComplete": "Backup completado de manera exitosa en el servidor {}", "backupComplete": "Respaldo completado de manera exitosa para el servidor {}",
"backupStarted": "Backup iniciado para el servidor {}", "backupStarted": "Respaldo iniciado para el servidor {}",
"downloadLogs": "Descargar registros de soporte?", "downloadLogs": "¿Descargar registros de soporte?",
"finishedPreparing": "Terminamos la preparación de tus registros de soporte. Por favor presione el boton para descargar.", "finishedPreparing": "Terminamos la preparación de tus registros de soporte. Por favor presione el botón para descargar.",
"logout": "Cerrar Sesión", "logout": "Cerrar Sesión",
"preparingLogs": " Por favor espere mientras preparamos los registros. Le enviaremos una notificación cuando estén listos. Esto puede tomar un rato en implementaciones grandes.", "preparingLogs": " Por favor espere mientras preparamos los registros. Le enviaremos una notificación cuando estén listos. Esto puede tomar un rato en implementaciones grandes.",
"supportLogs": "Registros de soporte" "supportLogs": "Registros de soporte"
}, },
"offline": {
"offline": "Desconectado",
"pleaseConnect": "Conéctate a Internet para usar Crafty."
},
"panelConfig": { "panelConfig": {
"adminControls": "Controles de administrador", "adminControls": "Controles de administrador",
"allowedServers": "Servidores Habilitados", "allowedServers": "Servidores Permitidos",
"assignedRoles": "Roles asignados", "assignedRoles": "Grupos asignados",
"cancel": "Cancelar", "cancel": "Cancelar",
"clearComms": "Limpiar comandos sin ejecutar.", "clearComms": "Limpiar comandos sin ejecutar.",
"select": "Seleccionar",
"apply": "Aplicar",
"delete": "Eliminar", "delete": "Eliminar",
"edit": "Editar", "edit": "Editar",
"enabled": "Habilitado", "enabled": "Habilitado",
"newRole": "Agregar un nuevo rol", "match": "Las contraseñas deben coincidir",
"newRole": "Agregar nuevo Grupo",
"newUser": "Agregar nuevo Usuario", "newUser": "Agregar nuevo Usuario",
"pageTitle": "Configuración del panel", "pageTitle": "Configuración del panel",
"role": "Rol", "role": "Grupo",
"roles": "Roles", "roles": "Grupos",
"roleUsers": "Usuarios del rol", "roleUsers": "Usuarios del Grupo",
"save": "Guardar", "save": "Guardar",
"superConfirm": "Proceder solamente si queres que este usuario tenga acceso a TODO(todas las cuentas de usuarios, servidores, configuración del panel, etc...) Hasta puede quitarte tu permiso de superuser.", "superConfirm": "Proceder solamente si quieres que este usuario tenga acceso a TODO (todas las cuentas de usuarios, servidores, configuración del panel, etc.) Hasta puede quitarte tu permiso de super usuario.",
"superConfirmTitle": "Habilitar superusers? Estas seguro?", "superConfirmTitle": "¿Activar super usuario? ¿Seguro?",
"user": "Usuario", "user": "Usuario",
"users": "Usuarios" "users": "Usuarios",
"title": "Configuración de Crafty",
"enableLang": "Activar todos los Idiomas",
"noMounts": "No mostrar Montajes en panel de Control",
"globalServer": "Directorio Global de Servidores",
"globalExplain": "Donde Crafty almacena todos los archivos de tu servidor. (Juntaremos la ruta con /servers/[uuid de servidor])"
},
"customLogin": {
"customLoginPage": "Personalizar la página de logueo",
"loginImage": "Subir una imagen de fondo para la pantalla de logueo.",
"labelLoginImage": "Elige tu fondo para la pantalla de logueo",
"backgroundUpload": "Subir imagen de fondo",
"loginBackground": "Imagen de fondo de logueo",
"loginOpacity": "Seleccionar la opacidad de la ventana de logueo",
"select": "Seleccionar",
"apply": "Aplicar",
"delete": "Eliminar",
"selectImage": "Seleccionar una imagen",
"preview": "Previsualizar",
"pageTitle": "Página de logueo personalizada"
}, },
"rolesConfig": { "rolesConfig": {
"config": "Configuracion de roles", "config": "Configuración de grupos",
"configDesc": "Aca podes cambiar la configuracion de los roles", "configDesc": "Aquí puedes cambiar la configuración de los grupos",
"configUpdate": "Actualizado: ", "configUpdate": "Actualizado: ",
"created": "Creado: ", "created": "Creado: ",
"delRole": "Eliminar Rol", "delRole": "Eliminar Grupo",
"doesNotExist": "No podes elminiar algo que todavia no existe", "doesNotExist": "No puedes eliminar algo que todavía no existe",
"pageTitle": "Editar Rol", "pageTitle": "Editar Grupo",
"pageTitleNew": "Nuevo Rol", "pageTitleNew": "Nuevo Grupo",
"permAccess": "Acceso?", "permAccess": "¿Acceso?",
"permName": "Nombre de los permisos", "permName": "Nombre del permiso",
"permsServer": "Permisos que tiene este rol para esos servidores específicos.", "permsServer": "Permisos que tiene este grupo para los servidores especificados.",
"roleConfigArea": "Zona de configuración de roles", "roleConfigArea": "Área de configuración de grupos",
"roleDesc": "Como te gustaria llamar a este rol?", "roleDesc": "¿Como te gustaría llamar a este grupo?",
"roleName": "Nombre del rol: ", "roleName": "Nombre del grupo: ",
"rolePerms": "Permisos del rol", "rolePerms": "Permisos del grupo",
"roleServers": "Servidores Habilitados", "roleServers": "Servidores Permitidos",
"roleTitle": "Configuración de Roles", "roleTitle": "Configuración de Grupos",
"roleUserName": "Nombre de usuario", "roleUserName": "Nombre de usuario",
"roleUsers": "Usuarios del rol: ", "roleUsers": "Usuarios del grupo: ",
"serverAccess": "Acceso?", "serverAccess": "¿Acceso?",
"serverName": "Nombre del servidor", "serverName": "Nombre del servidor",
"serversDesc": "Servidores a los que este rol puede acceder" "serversDesc": "Servidores a los que este grupo puede acceder"
}, },
"serverBackups": { "serverBackups": {
"backupAtMidnight": "¿Copia de seguridad automática a medianoche?", "backupAtMidnight": "¿Copia de seguridad automática a medianoche?",
"backupNow": Haga una copia de seguridad ahora!", "backupNow": Respalde ahora!",
"backupTask": "Se ha iniciado una tarea de copia de seguridad.", "backupTask": "Se ha iniciado una tarea de copia de seguridad.",
"cancel": "Cancelar", "cancel": "Cancelar",
"clickExclude": "Click para seleccionar las Exclusiones", "clickExclude": "Click para seleccionar las Exclusiones",
"compress": "Comprimir el Backup", "compress": "Comprimir la copia",
"confirm": "Confirmar", "confirm": "Confirmar",
"confirmDelete": "¿Quieres eliminar esta copia de seguridad? Esto no se puede deshacer.", "confirmDelete": "¿Quieres eliminar esta copia de seguridad? Esto no se puede deshacer.",
"confirmRestore": "Esta seguro de que quiere restaurar desde este backup. Todos los archivos actuales del servidor seran cambiados al estado del backup y seran irrecuperables.", "confirmRestore": "¿Seguro que quiere restaurar desde este respaldo?. Todos los archivos del servidor actuales serán cambiados al estado del respaldo y serán irrecuperables.",
"currentBackups": "Copias de seguridad actuales", "currentBackups": "Copias de seguridad actuales",
"delete": "Eliminar", "delete": "Eliminar",
"destroyBackup": "¿Destruir copia de seguridad \" + file_to_del + \"?", "destroyBackup": "¿Destruir copia de seguridad \" + file_to_del + \"?",
"download": "Descargar", "download": "Descargar",
"excludedBackups": "Rutas Excluidas: ", "excludedBackups": "Rutas Excluidas: ",
"excludedChoose": "Elija las rutas que desea excluir de sus backups", "excludedChoose": "Elige las rutas que desea excluir de los respaldos",
"exclusionsTitle": "Exclusiones del backup.", "exclusionsTitle": "Exclusiones en respaldos.",
"maxBackups": "Máxima cantidad de Copias de seguridad", "maxBackups": "Cantidad máxima de respaldos",
"maxBackupsDesc": "Crafty no almacenará más de N copias de seguridad, eliminando la más antigua (Ingrese 0 para mantenerlas todas)", "maxBackupsDesc": "Crafty no almacenará más de N copias de seguridad, eliminando la más antigua. (Sin límite: 0)",
"options": "Opciones", "options": "Opciones",
"path": "Dirección", "path": "Ruta",
"restore": "Restaurar", "restore": "Restaurar",
"restoring": "Restaurando copia de seguridad. Esto puede tomar un tiempo. Sea paciente.", "restoring": "Restaurando copia de seguridad. Esto puede tomar un tiempo. Sea paciente.",
"save": "Guardar", "save": "Guardar",
"shutdown": "Apague el servidor durante la duración de la copia del backup.", "shutdown": "Apagar el servidor durante la duración de la copia del respaldo.",
"size": "Tamaño", "size": "Tamaño",
"storageLocation": "Ubicación de almacenamiento", "storageLocation": "Ubicación de almacenamiento",
"storageLocationDesc": "¿Dónde quieres almacenar las copias de seguridad?" "storageLocationDesc": "¿Dónde quieres almacenar las copias de seguridad?",
}, "before": "Comando ejecutado antes del respaldo",
"after": "Comando ejecutado después del respaldo"
},
"serverConfig": { "serverConfig": {
"bePatientDelete": "Tenga paciencia mientras eliminamos su servidor del panel de Crafty. Esta pantalla se cerrará en unos momentos.", "bePatientDelete": "Tenga paciencia mientras eliminamos su servidor del panel de Crafty. Esta pantalla se cerrará en unos momentos.",
"bePatientDeleteFiles": "Tenga paciencia mientras eliminamos su servidor del panel de Crafty y eliminamos todos los archivos. Esta pantalla se cerrará en unos momentos.", "bePatientDeleteFiles": "Tenga paciencia mientras eliminamos su servidor del panel de Crafty y eliminamos todos los archivos. Esta pantalla se cerrará en unos momentos.",
"bePatientUpdate": "Tenga paciencia mientras actualizamos el servidor. El tiempo de descarga puede variar según la velocidad del Internet...<br /> Esta pantalla se actualizará en unos momentos.", "bePatientUpdate": "Tenga paciencia mientras actualizamos el servidor. El tiempo de descarga puede variar según la velocidad del Internet...<br /> Esta pantalla se actualizará en unos momentos.",
"cancel": "Cancelar", "cancel": "Cancelar",
"crashTime": "Tiempo de espera por crash", "crashTime": "Tiempo de espera por crasheo",
"crashTimeDesc": "Cuanto tiempo se debe esperar después de que crafty considere su servidor como crasheado?", "crashTimeDesc": "¿Cuanto tiempo esperar para considerar el servidor como crasheado?",
"deleteFilesQuestion": "¿Eliminar archivos del servidor de la máquina?", "deleteFilesQuestion": "¿Eliminar archivos del servidor del host?",
"deleteFilesQuestionMessage": "¿Le gustaría que Crafty elimine todos los archivos del servidor de la máquina host? <br><br><strong>Esto incluye backups del servidor.</strong>", "deleteFilesQuestionMessage": "¿Le gustaría que Crafty elimine todos los archivos del servidor de la máquina host? <br><br><strong>Esto incluye respaldos del servidor.</strong>",
"deleteServer": "Eliminar Servidor", "deleteServer": "Eliminar Servidor",
"deleteServerQuestion": "¿Eliminar Servidor?", "deleteServerQuestion": "¿Eliminar Servidor?",
"deleteServerQuestionMessage": "¿Estás seguro de que desea eliminar este servidor? Después de esto no hay vuelta atrás...", "deleteServerQuestionMessage": "¿Estás seguro de que desea eliminar este servidor? Después de esto no hay vuelta atrás...",
@ -292,97 +333,109 @@
"exeUpdateURLDesc": "URL de descarga directa para actualizaciones.", "exeUpdateURLDesc": "URL de descarga directa para actualizaciones.",
"javaNoChange": "No sobrescribir", "javaNoChange": "No sobrescribir",
"javaVersion": "Sobrescribir versión de Java.", "javaVersion": "Sobrescribir versión de Java.",
"javaVersionDesc": "Si vas a sobrescribir la versión de Java, Asegúrese de que ponga la localizacion de java con ' ' (comillas).", "javaVersionDesc": "Si vas a sobrescribir la versión de Java, Asegúrese de rodear la ubicación con comillas. (Variable predeterminada 'java' excluida)",
"noDelete": "No, vuelve atrás.", "noDelete": "No, vuelve atrás.",
"noDeleteFiles": "No, solo remover del panel.", "noDeleteFiles": "No, solo remover del panel.",
"removeOldLogsAfter": "Eliminar registros antiguos después de", "removeOldLogsAfter": "Eliminar registros antiguos después de",
"removeOldLogsAfterDesc": "¿Cuántos días debe ser un archivo de registro para ser eliminado (0 es desactivado)?", "removeOldLogsAfterDesc": "¿Cuántos días debe tener un archivo de registro para ser eliminado? (Desactivar: 0)",
"save": "Guardar", "save": "Guardar",
"sendingDelete": "Eliminando servidor", "sendingDelete": "Eliminando servidor",
"sendingRequest": "Enviando tu solicitud...", "sendingRequest": "Enviando tu solicitud...",
"serverAutoStart": "Inicio automático del servidor", "serverAutoStart": "Inicio automático del servidor",
"serverAutostartDelay": "Retraso del inicio automático del servidor.", "serverAutostartDelay": "Retraso del inicio automático del servidor.",
"serverAutostartDelayDesc": "Tiempo de retraso antes del inicio automático (si está habilitado debajo)", "serverAutostartDelayDesc": "Tiempo de retraso antes del inicio automático (Si está habilitado debajo)",
"serverCrashDetection": "Detección de crasheos del Servidor", "serverCrashDetection": "Detección de crasheos del Servidor",
"serverExecutable": "Ejecutable del servidor", "serverExecutable": "Ejecutable del servidor",
"serverExecutableDesc": "El archivo ejecutable del servidor.", "serverExecutableDesc": "El archivo ejecutable del servidor.",
"serverExecutionCommand": "Comando de ejecución del Servidor", "serverExecutionCommand": "Comando de ejecución del Servidor",
"serverExecutionCommandDesc": "Se ejecutara en una terminal oculta para iniciar el servidor.", "serverExecutionCommandDesc": "Se ejecutará en una terminal oculta para iniciar el servidor.",
"serverIP": "Server IP", "serverIP": "IP del Servidor",
"serverIPDesc": "IP a la que Crafty debería conectarse para obtener estadísticas (pruebe con una ip real en lugar de 127.0.0.1 si tienes problemas)", "serverIPDesc": "IP a la que Crafty debería conectarse para obtener estadísticas (Pruebe con una IP real en lugar de 127.0.0.1 si tienes problemas)",
"serverLogLocation": "Ubicación del registro del servidor", "serverLogLocation": "Ubicación del registro del servidor",
"serverLogLocationDesc": "Dirección absoluta al archivo de registro", "serverLogLocationDesc": "Ruta absoluta al archivo de registro",
"serverName": "Nombre del servidor", "serverName": "Nombre del servidor",
"serverNameDesc": "¿Cómo quieres nombrar el servidor?", "serverNameDesc": "¿Cómo quieres nombrar el servidor?",
"serverPath": "Directorio de trabajo del servidor", "serverPath": "Ruta de trabajo del servidor",
"serverPathDesc": "Dirección absoluta del servidor (sin incluir el ejecutable)", "serverPathDesc": "Ruta absoluta del servidor (sin incluir el ejecutable)",
"serverPort": "Puerto del Servidor", "serverPort": "Puerto del Servidor",
"serverPortDesc": "Puerto al que Crafty debería conectarse para obtener estadísticas", "serverPortDesc": "Puerto al que Crafty debería conectarse para obtener estadísticas",
"serverStopCommand": "Comando de detención del Servidor", "serverStopCommand": "Comando de detención del Servidor",
"serverStopCommandDesc": "Comando que enviar al programa para detener el servidor.", "serverStopCommandDesc": "Comando que enviar al programa para detener el servidor.",
"stopBeforeDeleting": "Detenga el servidor antes de eliminarlo.", "showStatus": "Mostrar en la pagina de estados publicos",
"stopBeforeDeleting": "Por favor, detenga el servidor antes de eliminarlo.",
"update": "Actualizar Ejecutable", "update": "Actualizar Ejecutable",
"yesDelete": "Si, borralo.", "yesDelete": "Sí, borralo.",
"yesDeleteFiles": "Si, borra los archivos." "yesDeleteFiles": "Sí, borra los archivos.",
"shutdownTimeout": "Tiempo de espera al apagar",
"timeoutExplain1": "Cuánto tiempo esperará Crafty a que su servidor se apague después de ejecutar el",
"timeoutExplain2": "comando antes de forzar cerrar el proceso.",
"statsHint1": "El puerto que ejecuta su servidor debe ir aquí. Crafty abre una conexión a su servidor para obtener estadísticas.",
"statsHint2": "Esto no cambia el puerto de su servidor. Aún debe cambiar el puerto en el archivo de configuración de su servidor.",
"ignoredExits": "Codigos de salida al crashear ignorados",
"ignoredExitsExplain": "Codigos de salida que Crafty deberia ignorar como un 'apagado' normal (separarados por comas)"
}, },
"serverConfigHelp": { "serverConfigHelp": {
"desc": "Here is where you can change the configuration of your server", "desc": "Aquí es donde puede cambiar la configuración de su servidor",
"perms": [ "perms": [
"It is recommended to <code>NOT</code> change the paths of a server managed by Crafty.", "Se recomienda <code>NO</code> cambiar las rutas de un servidor administrado por Crafty.",
"Changing paths <code>CAN</code> break things, especially on Linux type operating systems where file permissions are more locked down.", "Cambiar las rutas <code>PUEDE</code> romper cosas, especialmente en sistemas operativos tipo Linux donde los permisos de archivo están más bloqueados.",
"<br /><br/>", "<br /><br/>",
"If you feel you have to change a where a server is located you may do so as long as you give the \"crafty\" user permission to read / write to the server path.", "Si cree que tiene que cambiar la ubicación de un servidor, puede hacerlo siempre que le dé permiso al usuario \"Crafty\" para leer/escribir en la ruta del servidor.",
"<br />", "<br />",
"<br />", "<br />",
"On Linux this is best done by executing the following:<br />", "En Linux, esto se hace mejor ejecutando lo siguiente:<br />",
"<code>", "<code>",
" sudo chown crafty:crafty /path/to/your/server -R<br />", " sudo chown crafty:crafty /ruta/del/servidor -R<br />",
" sudo chmod 2775 /path/to/your/server -R<br />", " sudo chmod 2775 /ruta/del/servidor -R<br />",
"</code>" "</code>"
], ],
"title": "Server Config Area" "title": "Área de configuración del servidor"
}, },
"serverDetails": { "serverDetails": {
"backup": "Backup", "backup": "Respaldos",
"config": "Configuración", "config": "Configuración",
"files": "Archivos", "files": "Archivos",
"logs": "Registros", "logs": "Registros",
"playerControls": "Gestionar jugadores", "playerControls": "Gestionar jugadores",
"schedule": "Programación Tareas", "schedule": "Programar Tareas",
"serverDetails": "Detalles del Servidor", "serverDetails": "Detalles del Servidor",
"terminal": "Terminal" "terminal": "Terminal",
"metrics": "Métricas",
"reset": "Reiniciar Desplazamiento",
"filter": "Filtrar Registros",
"filterList": "Palabras filtradas"
}, },
"serverFiles": { "serverFiles": {
"clickUpload": "Click aquí para seleccionar tus archivos", "clickUpload": "Click aquí para seleccionar tus archivos",
"close": "Cerrar", "close": "Cerrar",
"createDir": "Crear directorio", "createDir": "Crear directorio",
"createDirQuestion": "Que nombre quiere darle al nuevo directorio?", "createDirQuestion": "¿Que nombre quieres para el nuevo directorio?",
"createFile": "Crear archivo", "createFile": "Crear archivo",
"createFileQuestion": "Que nombre quiere darle al nuevo archivo?", "createFileQuestion": "¿Que nombre quieres para el nuevo archivo?",
"default": "Predeterminado", "default": "Predeterminado",
"delete": "Eliminar", "delete": "Eliminar",
"deleteItemQuestion": "¿Estás seguro de que quieres eliminar \" + name + \"?", "deleteItemQuestion": "¿Estás seguro de que quieres eliminar \" + name + \"?",
"deleteItemQuestionMessage": "¡Estas eliminando \\\"\" + path + \"\\\"!<br/><br/>¡Esta acción será irreversible y se perderá para siempre!", "deleteItemQuestionMessage": "¡Estas eliminando \\\"\" + path + \"\\\"!<br/><br/>¡Esta acción será irreversible y se perderá para siempre!",
"download": "Descargar", "download": "Descargar",
"editingFile": "Editar archivo", "editingFile": "Editando archivo",
"error": "Error al encontrar archivos", "error": "Error al encontrar archivos",
"fileReadError": "Error de lectura del archivo", "fileReadError": "Error de lectura del archivo",
"files": "Archivos", "files": "Archivos",
"keybindings": "Atajos de teclado", "keybindings": "Atajos de teclado",
"loadingRecords": "Cargando Archivos...", "loadingRecords": "Cargando Archivos...",
"noDelete": "No", "noDelete": "No",
"noscript": "El administrador de archivos no funciona sin JavaScript ", "noscript": "El administrador de archivos no funciona sin JavaScript",
"rename": "Renombrar", "rename": "Renombrar",
"renameItemQuestion": "Cuál debería ser el nuevo nombre?", "renameItemQuestion": "¿Cuál debería ser el nuevo nombre?",
"save": "Guardar", "save": "Guardar",
"size": "Alternar tamaño del editor.", "size": "Alternar tamaño del editor.",
"stayHere": "NO SALGA DE ESTA PÁGINA", "stayHere": "¡NO SALGA DE ESTA PÁGINA!",
"unsupportedLanguage": "Advertencia: este no es un tipo de archivo admitido", "unsupportedLanguage": "Advertencia: Este no es un tipo de archivo admitido",
"unzip": "Descomprimir (UnZip)", "unzip": "Descomprimir (UnZip)",
"upload": "Subir", "upload": "Subir",
"uploadTitle": "Subir archivos a: ", "uploadTitle": "Subir archivos a: ",
"waitUpload": "Espera mientras subimos tus archivos... Esto puede tomar un tiempo.", "waitUpload": "Espera mientras subimos tus archivos... Esto puede tomar un tiempo.",
"yesDelete": "Si, entiendo las consecuencias" "yesDelete": "Sí, entiendo las consecuencias"
}, },
"serverPlayerManagement": { "serverPlayerManagement": {
"bannedPlayers": "Jugadores Baneados", "bannedPlayers": "Jugadores Baneados",
@ -390,39 +443,57 @@
"players": "Jugadores" "players": "Jugadores"
}, },
"serverScheduleConfig": { "serverScheduleConfig": {
"backup": "Backup del Servidor", "backup": "Respaldar Servidor",
"select": "Básico, / Cron / En cadena",
"basic": "Básico", "basic": "Básico",
"children": "Tareas 'hijos' enlazados: ", "children": "Tareas 'hijos' enlazados: ",
"command": "Comando", "command": "Comando",
"command-explain": "Que comandó quiere que sea ejecutado, no incluya el '/'", "command-explain": "Que comandó quiere que sea ejecutado, no incluya el '/'",
"cron": "Cron", "cron": "Cron",
"cron-explain": "Ponga su configuración de cron. -- NOTA: Pagina recomendada: 'https://crontab.guru'", "cron-explain": "Ponga su cadena de CRON. -- NOTA: Página recomendada: 'https://crontab.guru'",
"custom": "Comando Customizado", "custom": "Comando Personalizado",
"days": "Días", "days": "Días",
"enabled": "Habilitado", "enabled": "Habilitado",
"hours": "Horas", "hours": "Horas",
"interval": "Intervalo", "interval": "Intervalo",
"interval-explain": "Con qué frecuencia quieres que se ejecute esta tarea?", "interval-explain": "¿Con qué frecuencia quieres que se ejecute esta tarea?",
"minutes": "Minutos", "minutes": "Minutos",
"offset": "Retraso", "offset": "Retraso",
"offset-explain": "Cuanto tiempo se debe esperar para ejecutar esta tarea después de la primera? (En Segundos).", "offset-explain": "¿Cuanto tiempo se debe esperar para ejecutar esta tarea después de la primera? (En Segundos).",
"one-time": "Borrar después de su ejecución?", "one-time": "¿Borrar después de su ejecución?",
"parent": "Seleccione una tarea programada 'padre'", "parent": "Seleccione una tarea programada 'padre'",
"parent-explain": "Qué tarea programada debería activar esta?", "parent-explain": "¿Qué tarea programada debería activar esta?",
"reaction": "Reaccion", "reaction": "Reacción",
"restart": "Reiniciar el Servidor", "restart": "Reiniciar el Servidor",
"start": "Iniciar el Servidor", "start": "Iniciar el Servidor",
"stop": "Apagar el Servidor", "stop": "Apagar el Servidor",
"time": "Horario", "time": "Horario",
"time-explain": "A que hora quiere que la tarea programada se ejecute?" "time-explain": "¿A que hora quiere que la tarea programada se ejecute?"
}, },
"serverSchedules": { "serverSchedules": {
"areYouSure": "Borrar tarea programada?", "scheduledTasks": "Tareas programadas",
"create": "Crear nueva tarea programada",
"name": "Nombre",
"action": "Acción",
"command": "Comando",
"interval": "Intervalo",
"nextRun": "Siguiente ejecución",
"enabled": "Habilitado",
"edit": "Editar",
"every": "Cada",
"yes": "Sí",
"no": "No",
"cron": "Cadena CRON",
"details": "Detalles de la tarea",
"child": "Tarea hija con ID ",
"areYouSure": "¿Borrar tarea programada?",
"close": "Cerrar",
"delete": "Eliminar",
"cancel": "Cancelar", "cancel": "Cancelar",
"cannotSee": "No puede pude ver todo?", "cannotSee": "¿No puede ver todo?",
"cannotSeeOnMobile": "Intente hacer clic en una tarea programada para obtener todos los detalles.", "cannotSeeOnMobile": "Intente hacer click en una tarea programada para obtener todos los detalles.",
"confirm": "Confirmar", "confirm": "Confirmar",
"confirmDelete": "Estás seguro de que desea eliminar esta tarea programada? Esto no se puede deshacer." "confirmDelete": "¿De verdad desea eliminar esta tarea programada? Esto no se puede deshacer."
}, },
"serverStats": { "serverStats": {
"cpuUsage": "Uso de CPU", "cpuUsage": "Uso de CPU",
@ -443,33 +514,41 @@
}, },
"serverTerm": { "serverTerm": {
"commandInput": "Introducir tu comando", "commandInput": "Introducir tu comando",
"delay-explained": "El agente/servicio ha recientemente iniciado y está retrasando el inicio de la instancia del servidor de Minecraft.", "delay-explained": "El agente/servicio se inicio recientemente y está retrasando iniciar la instancia del servidor de Minecraft.",
"downloading": "Descargando...", "downloading": "Descargando...",
"restart": "Reiniciar", "restart": "Reiniciar",
"sendCommand": "Enviar comando", "sendCommand": "Enviar comando",
"start": "Iniciar", "start": "Iniciar",
"starting": "Inicio-retrasado", "starting": "Inicio-retrasado",
"stop": "Detener", "stop": "Detener",
"stopScroll": "Detener Scrolling automatico", "stopScroll": "Detener Desplazamiento automático",
"updating": "Actualizando..." "updating": "Actualizando...",
"installing": "Instalando..."
},
"serverMetrics": {
"resetZoom": "Reiniciar Zoom",
"zoomHint1": "Para hacer zoom en el gráfico, mantén presionada la tecla shift y luego usa la rueda de desplazamiento.",
"zoomHint2": "Alternativamente, mantén presionada la tecla shift, luego haz click y arrastra el área en la que deseas hacer zoom."
}, },
"serverWizard": { "serverWizard": {
"absoluteServerPath": "Dirección absoluta del servidor", "absoluteServerPath": "Ruta absoluta del servidor",
"absoluteZipPath": "Dirección absoluta del servidor", "absoluteZipPath": "Ruta absoluta servidor",
"addRole": "Añadir Servidor a grupos existentes..", "addRole": "Añadir Servidor a grupos existentes...",
"autoCreate": "Si no se selecciona ninguno, ¡Crafty creara uno!", "autoCreate": "Si ninguno es seleccionado, ¡Crafty creara uno!",
"bePatient": "Por favor tenga paciencia, ya que estamos ' + (importing ? 'import' : 'download') + ' el servidor.", "bePatient": "Por favor tenga paciencia, mientras se ' + (importing ? 'importa' : 'descarga') + ' el servidor.",
"buildServer": "Construir Servidor!", "buildServer": "¡Crear Servidor!",
"clickRoot": "Click aquí para seleccionar el directorio raiz.", "clickRoot": "Click aquí para seleccionar el directorio raiz.",
"close": "Cerrar", "close": "Cerrar",
"defaultPort": "25565 (Por defecto)", "defaultPort": "25565 (Por defecto)",
"downloading": "Descargando Servidor...", "downloading": "Descargando Servidor...",
"explainRoot": "Por favor, haga click en el botón de abajo para seleccionar el directorio raiz de su servidor.", "explainRoot": "Por favor, haga click en el botón debajo para seleccionar el directorio raíz de su servidor.",
"importing": "Importando Servidor...", "importing": "Importando Servidor...",
"importServer": "Importar Servidor existente", "importServer": "Importar Servidor existente",
"importServerButton": "Importar Servidor!", "importServerButton": "¡Importar Servidor!",
"importZip": "Importar desde archivo Zip", "importZip": "Importar desde archivo Zip",
"maxMem": "Memoria máxima", "uploadZip": "Subir archivo Zip para importar servidor",
"labelZipFile": "Elige tu archivo Zip",
"maxMem": "Memoria máxima",
"minMem": "Memoria mínima", "minMem": "Memoria mínima",
"myNewServer": "Mi nuevo Servidor", "myNewServer": "Mi nuevo Servidor",
"newServer": "Crear Servidor", "newServer": "Crear Servidor",
@ -479,62 +558,68 @@
"save": "Guardar", "save": "Guardar",
"selectRole": "Seleccionar Grupo(s)", "selectRole": "Seleccionar Grupo(s)",
"selectRoot": "Seleccione el directorio raíz del archivo.", "selectRoot": "Seleccione el directorio raíz del archivo.",
"selectType": "Selecciona un tipo", "selectType": "Tipo de servidor (Vanilla, Modificado, Mods, etc.)",
"selectVersion": "Selecciona una versión", "selectVersion": "Selecciona una versión",
"selectZipDir": "Seleccione el directorio donde quiere que se extraigan los archivos.", "selectZipDir": "Seleccione el directorio del archivo que quiere extraer el contenido.",
"serverJar": "Archivo Jar del servidor", "serverJar": "Archivo Jar del servidor",
"serverName": "Nombre del servidor", "serverName": "Nombre del servidor",
"serverPath": "Dirección del servidor", "serverPath": "Ruta del servidor",
"serverPort": "Puerto del servidor", "serverPort": "Puerto del servidor",
"serverType": "Tipo de servidor", "serverType": "Tipo de servidor",
"serverVersion": "Versión del servidor", "serverVersion": "Versión del servidor",
"serverUpload": "Subir servidor comprimido",
"sizeInGB": "Tamaño en GB", "sizeInGB": "Tamaño en GB",
"zipPath": "Dirección del servidor" "uploadButton": "Subir",
"zipPath": "Ruta del servidor"
}, },
"sidebar": { "sidebar": {
"contribute": "Contribuir", "contribute": "Contribuir",
"credits": "Creditos", "credits": "Créditos",
"dashboard": "Panel de control", "dashboard": "Panel de control",
"documentation": "Documentación", "documentation": "Documentación",
"navigation": "Navegación", "navigation": "Navegación",
"newServer": "Crear nuevo Servidor", "newServer": "Crear nuevo Servidor",
"servers": "Servers" "servers": "Servidores",
"inApp": "Documentación de la Aplicación"
}, },
"userConfig": { "userConfig": {
"apiKey": "Claves API", "apiKey": "Claves API",
"auth": "Autorizado? ", "auth": "¿Autorizado? ",
"config": "Configuración", "config": "Configuración",
"configArea": "Area de Configuracion de Usuarios", "configArea": "Área de Configuración de Usuario",
"configAreaDesc": "Aca modificas la configuración de todos tus usuarios.", "configAreaDesc": "Aquí modificas la configuración de este usuario.",
"confirmDelete": "Está seguro de que quiere borrar a este usuario? Esta acción es irreversible.", "confirmDelete": "¿Está seguro de que quiere eliminar a este usuario? Esta acción es irreversible.",
"craftyPermDesc": "Permisos de Crafty que tiene este usuario.", "craftyPermDesc": "Permisos de Crafty que tiene este usuario.",
"craftyPerms": "Permisos de Crafty: ", "craftyPerms": "Permisos de Crafty: ",
"created": "Creado: ", "created": "Creado: ",
"deleteUser": "Borrar Usuario: ", "deleteUser": "Eliminar Usuario: ",
"deleteUserB": "Borrar Usuario", "deleteUserB": "Eliminar Usuario",
"delSuper": "No puedes borrar un super user", "delSuper": "No puedes eliminar un super usuario",
"enabled": "Habilitado", "enabled": "Habilitado",
"gravDesc": "Este mail se va a usar solamente para conseguir el Gravatar. Crafty no va a usar esta información bajo ninguna circunstancia más que para conseguir el Gravatar™", "gravDesc": "Este mail será utilizado únicamente para obtener el Gravatar. Crafty no utilizará esta información bajo otra circunstancia aparte de obtener el Gravatar™",
"gravEmail": "Gravatar™ Email", "gravEmail": "Gravatar™ Email",
"lastIP": "Ultima IP: ", "lastIP": "Última IP: ",
"lastLogin": "Última Sesión: ", "lastLogin": "Última Sesión: ",
"lastUpdate": "Última Actualización: ", "lastUpdate": "Última Actualización: ",
"leaveBlank": "Para editar el usuario sin cambiar la contraseña, no completar nada.", "leaveBlank": "Para editar el usuario sin cambiar la contraseña, dejar en blanco.",
"member": "Miembro?", "member": "¿Miembro?",
"notExist": "No se puede borrar algo que no existe!", "notExist": "¡No se puede eliminar algo que no existe!",
"pageTitle": "Editar Usuario.", "pageTitle": "Editar Usuario.",
"pageTitleNew": "Crear Usuario.", "pageTitleNew": "Crear Usuario.",
"password": "Nueva Contraseña", "password": "Nueva Contraseña",
"permName": "Nombre del Permiso", "permName": "Nombre del Permiso",
"repeat": "Repita la contraseña.", "repeat": "Repita la contraseña.",
"roleName": "Nombre del rol.", "roleName": "Nombre del grupo.",
"super": "Super User", "super": "Super Usuario",
"userLang": "Idioma del usuario.", "userLang": "Idioma del usuario.",
"userTheme": "Tema de Interfaz",
"userName": "Nombre de usuario.", "userName": "Nombre de usuario.",
"userNameDesc": "Como quieres llamar a este usuario?", "userNameDesc": "¿Como quieres llamar a este usuario?",
"userRoles": "Roles del usuario.", "userRoles": "Grupos del usuario.",
"userRolesDesc": "Roles que tiene este usuario.", "userRolesDesc": "Grupos a los que este usuario pertenece.",
"userSettings": "Configuracion de Usuario.", "userSettings": "Configuración de Usuario.",
"uses": "Número de usos habilitados. (-1 == sin límite)." "uses": "Número de usos permitidos. (Sin límite: -1)",
"manager": "Administrador",
"selectManager": "Seleccionar Administrador para Usuario"
} }
} }

View File

@ -53,6 +53,20 @@
"translationTitle": "Język tłumaczenia", "translationTitle": "Język tłumaczenia",
"translator": "Tłumacze" "translator": "Tłumacze"
}, },
"customLogin": {
"apply": "Apply",
"backgroundUpload": "Załącz tło",
"customLoginPage": "Dostosuj stronę zalogowania",
"delete": "Usuń",
"labelLoginImage": "Wybierz swoje tło logowania",
"loginBackground": "Zdjęcie tła logowania",
"loginImage": "Prześlij tło dla ekranu logowania.",
"loginOpacity": "Wybierz przeźroczystość tła na ekranie logowania",
"pageTitle": "Własna strona logowania",
"preview": "Podgląd",
"select": "Zaznacz",
"selectImage": "Zaznacz zdjęcie"
},
"dashboard": { "dashboard": {
"actions": "Akcje", "actions": "Akcje",
"allServers": "Wszystkie serwery", "allServers": "Wszystkie serwery",
@ -75,6 +89,7 @@
"dashboard": "Dashboard", "dashboard": "Dashboard",
"delay-explained": "Crafty niedawno się odpalił, właczanie serwera będzie trochę opóźnione", "delay-explained": "Crafty niedawno się odpalił, właczanie serwera będzie trochę opóźnione",
"host": "Host", "host": "Host",
"installing": "Instalowanie...",
"kill": "Zabij proces", "kill": "Zabij proces",
"killing": "Zabijanie procesu...", "killing": "Zabijanie procesu...",
"lastBackup": "Ostatni:", "lastBackup": "Ostatni:",
@ -164,20 +179,33 @@
} }
}, },
"error": { "error": {
"agree": "Zgadzam się",
"bedrockError": "Pobieranie serwera bedrock jest teraz niedostępne. Proszę sprawdź",
"cancel": "Anuluj",
"contact": "Podrzebujesz pomocy? Zapraszamy na serwer discord Crafty Controler", "contact": "Podrzebujesz pomocy? Zapraszamy na serwer discord Crafty Controler",
"craftyStatus": "Status strony Craftyiego",
"cronFormat": "Nieprawidłowy format Cron",
"embarassing": "Oh, więc, to jest żenujące.", "embarassing": "Oh, więc, to jest żenujące.",
"error": "Błąd!", "error": "Błąd!",
"eulaAgree": "Czy się zgadzasz?", "eulaAgree": "Czy się zgadzasz?",
"eulaMsg": "Musisz się zgodzić na EULA. Kopia EULA Minecraft jest zalinkowana pod tą wiadomością.", "eulaMsg": "Musisz się zgodzić na EULA. Kopia EULA Minecraft jest zalinkowana pod tą wiadomością.",
"eulaTitle": "Zgódź się na EULA", "eulaTitle": "Zgódź się na EULA",
"fileError": "Plik musi być zdjęciem.",
"fileTooLarge": "Upload nie udany. Plik jest za duży. Skontaktuj się z administratorem dla pomocy.", "fileTooLarge": "Upload nie udany. Plik jest za duży. Skontaktuj się z administratorem dla pomocy.",
"hereIsTheError": "Tu jest problem", "hereIsTheError": "Tu jest problem",
"installerJava": "Nie udało się zainstalować {} : Serwery Forge wymagają Java. Wykryliśmy że Java nie jest zainstalowana. Proszę zainstaluj Javę a następnie serwer.",
"internet": "Zauważyliśmy że Crafty nie ma dostępu do internetu. Połączenie klientów z Craftym może być utrudnione.", "internet": "Zauważyliśmy że Crafty nie ma dostępu do internetu. Połączenie klientów z Craftym może być utrudnione.",
"migration": "Miejsce przechowywania serwerów zostało zmienione w Craftym. Włączanie serwerów zostanie zawieszone do zakończenia migracji",
"no-file": "Nie możemy znaleść żądanego pliku. Sprawdź ścieżkę. Czy Crafty ma odpowiednie uprawnienia?", "no-file": "Nie możemy znaleść żądanego pliku. Sprawdź ścieżkę. Czy Crafty ma odpowiednie uprawnienia?",
"noInternet": "Crafty ma problem połączenia się z internetem. Tworzenie serwerów zostało wyłączone. Sprawdź swoje połączenie z intenetem i spróbuj ponownie.",
"noJava": "Serwer {} nie mógł się odpalić z powodu: Zauważyliśmy że Java nie jest zainstalowana. Proszę zainstaluj Javę, a następnie włącz serwer.", "noJava": "Serwer {} nie mógł się odpalić z powodu: Zauważyliśmy że Java nie jest zainstalowana. Proszę zainstaluj Javę, a następnie włącz serwer.",
"not-downloaded": "Nie możemy znaleść twojego pliku serwera. Czy skończył się pobierać? Czy permisje są ustawione na wykonywanle?", "not-downloaded": "Nie możemy znaleść twojego pliku serwera. Czy skończył się pobierać? Czy permisje są ustawione na wykonywanle?",
"portReminder": "Zauważyliśmy że to jest pierwszy raz {} kiedy był włączony. Upewnij się że otworzyłeś port {} na swoim routerze/firewallu aby korzystać z tego poza domem.", "portReminder": "Zauważyliśmy że to jest pierwszy raz {} kiedy był włączony. Upewnij się że otworzyłeś port {} na swoim routerze/firewallu aby korzystać z tego poza domem.",
"privMsg": "i także ",
"serverJars1": "API Server Jars jest niedostępne. Proszę sprawdź",
"serverJars2": "dla najnowzsych informacji.",
"start-error": "Serwer {} nie mógł się odpalić z powodu: {}", "start-error": "Serwer {} nie mógł się odpalić z powodu: {}",
"superError": "Potrzebujesz uprawnienie Adminitsratora aby zakończyć tę akcję.",
"terribleFailure": "Okropny błąd!" "terribleFailure": "Okropny błąd!"
}, },
"footer": { "footer": {
@ -201,25 +229,38 @@
"preparingLogs": " Poczekaj kiedy my przygotowujemy twoje logi ... Wyślemy ci powiadomienie kiedy będą gotowe. To trochę zajmie na dużych serwerach.", "preparingLogs": " Poczekaj kiedy my przygotowujemy twoje logi ... Wyślemy ci powiadomienie kiedy będą gotowe. To trochę zajmie na dużych serwerach.",
"supportLogs": "Logi Pomocnicze" "supportLogs": "Logi Pomocnicze"
}, },
"offline": {
"offline": "Offline",
"pleaseConnect": "Proszę połącz się z internetem aby korzystać z Craftiego."
},
"panelConfig": { "panelConfig": {
"adminControls": "Ustawienia Admina", "adminControls": "Ustawienia Admina",
"allowedServers": "Zezwolone serwery", "allowedServers": "Zezwolone serwery",
"apply": "Akceptuj",
"assignedRoles": "Przypisane role", "assignedRoles": "Przypisane role",
"cancel": "Anuluj", "cancel": "Anuluj",
"clearComms": "Wyczyść nie wykonane komendy", "clearComms": "Wyczyść nie wykonane komendy",
"custom": "Dostosuj Craftiego",
"delete": "Usuń", "delete": "Usuń",
"edit": "Edytuj", "edit": "Edytuj",
"enabled": "Wlączone", "enabled": "Wlączone",
"enableLang": "Włącz wszystkie języki",
"globalExplain": "Gdzie Crafty trzyma wszystkie twoje serwery. (dołączymy /servers/[uuid of server] do ścieżki)",
"globalServer": "Globalna ścieżka serwerów",
"json": "Config.json",
"match": "Hasła muszą być takie same", "match": "Hasła muszą być takie same",
"newRole": "Dodaj nową role", "newRole": "Dodaj nową role",
"newUser": "Dodaj nowego użytkownika", "newUser": "Dodaj nowego użytkownika",
"noMounts": "Nie pokazuj dysków w panelu",
"pageTitle": "Ustawienia Panelu", "pageTitle": "Ustawienia Panelu",
"role": "Role", "role": "Role",
"roles": "Roles", "roles": "Roles",
"roleUsers": "Role użytkownika", "roleUsers": "Role użytkownika",
"save": "Zapisz", "save": "Zapisz",
"select": "Wybierz",
"superConfirm": "Zapisz jeśli chcesz aby ten użytkownik miał dostęp do WSZYSTKIEGO (wszyscy użytkownicy, konta, serwery, ustawienia panelu, itp.). Mogą oni nawet usunąć twoje prawa SuperUżytkownika.", "superConfirm": "Zapisz jeśli chcesz aby ten użytkownik miał dostęp do WSZYSTKIEGO (wszyscy użytkownicy, konta, serwery, ustawienia panelu, itp.). Mogą oni nawet usunąć twoje prawa SuperUżytkownika.",
"superConfirmTitle": "Włączyć SuperUżytkownika? Jesteś pewien?", "superConfirmTitle": "Włączyć SuperUżytkownika? Jesteś pewien?",
"title": "Konfiguracja Craftiego",
"user": "Użytkownik", "user": "Użytkownik",
"users": "użytkownicy" "users": "użytkownicy"
}, },
@ -243,14 +284,17 @@
"roleTitle": "Ustawienia roli", "roleTitle": "Ustawienia roli",
"roleUserName": "Nazwa użytkownika", "roleUserName": "Nazwa użytkownika",
"roleUsers": "Dostęp do ról: ", "roleUsers": "Dostęp do ról: ",
"selectManager": "Wybierz zarządce dla tej roli",
"serverAccess": "Dostęp?", "serverAccess": "Dostęp?",
"serverName": "Nazwa serwera", "serverName": "Nazwa serwera",
"serversDesc": "Serwery które mają tą role mają dostęp" "serversDesc": "Serwery które mają tą role mają dostęp"
}, },
"serverBackups": { "serverBackups": {
"after": "Wykonaj tę komendę po backupie",
"backupAtMidnight": "Auto-backup o północy?", "backupAtMidnight": "Auto-backup o północy?",
"backupNow": "Backup Teraz!", "backupNow": "Backup Teraz!",
"backupTask": "Backup został rozpoczęty.", "backupTask": "Backup został rozpoczęty.",
"before": "Wykonaj tę komendę przed backupem",
"cancel": "Anuluj", "cancel": "Anuluj",
"clickExclude": "Kliknij aby zaznaczyć wyjątki", "clickExclude": "Kliknij aby zaznaczyć wyjątki",
"compress": "Skompresuj backup", "compress": "Skompresuj backup",
@ -290,6 +334,8 @@
"deleteServerQuestionMessage": "Jesteś pewien że chcesz usunąć ten serwer? To jest nie odwracalne...", "deleteServerQuestionMessage": "Jesteś pewien że chcesz usunąć ten serwer? To jest nie odwracalne...",
"exeUpdateURL": "URL pliku egzekucyjnego serwera", "exeUpdateURL": "URL pliku egzekucyjnego serwera",
"exeUpdateURLDesc": "Bezpośredni link dla aktualizacji.", "exeUpdateURLDesc": "Bezpośredni link dla aktualizacji.",
"ignoredExits": "Ignoruj kody błędu przy crashu",
"ignoredExitsExplain": "Kody błędu Crafty powinien rozumieć jako `stop` (odzielone przecinkiem)",
"javaNoChange": "Nie nadpisuj", "javaNoChange": "Nie nadpisuj",
"javaVersion": "Nadpisz wersję Javy", "javaVersion": "Nadpisz wersję Javy",
"javaVersionDesc": "Jeśli chcesz nadpisać wersję javy. Upewnij się że twoja wersja javy w 'komendzie egzekucyjnej' jest zawinięta w cytaty (podstatowa zmienna 'java' jest wykluczona )", "javaVersionDesc": "Jeśli chcesz nadpisać wersję javy. Upewnij się że twoja wersja javy w 'komendzie egzekucyjnej' jest zawinięta w cytaty (podstatowa zmienna 'java' jest wykluczona )",
@ -320,7 +366,13 @@
"serverPortDesc": "Port na jakim Crafty ma szukać statystyk", "serverPortDesc": "Port na jakim Crafty ma szukać statystyk",
"serverStopCommand": "Komenda wyłączenia serwera", "serverStopCommand": "Komenda wyłączenia serwera",
"serverStopCommandDesc": "Komenda, którą wyśle crafty aby wyłączyć serwer", "serverStopCommandDesc": "Komenda, którą wyśle crafty aby wyłączyć serwer",
"showStatus": "Pokazuj na publicznej stronie statusu",
"shutdownTimeout": "Czas oczekiwania na zamknięcie",
"statsHint1": "Port na którym operuje serwer. W ten sposób Crafty ma statystyki.",
"statsHint2": "To nie zmienia portu serwera. Wciąż musisz go manualnie zmienić w server.properties.",
"stopBeforeDeleting": "Zatrzymaj serwer przed jego usunięciem", "stopBeforeDeleting": "Zatrzymaj serwer przed jego usunięciem",
"timeoutExplain1": "Jak długo musi Crafty czekać po `wyłączeniu` serwera",
"timeoutExplain2": "aby wymusić zamknięcie.",
"update": "Aktualizuj plik serwera", "update": "Aktualizuj plik serwera",
"yesDelete": "Tak, usuń", "yesDelete": "Tak, usuń",
"yesDeleteFiles": "Tak, usuń pliki" "yesDeleteFiles": "Tak, usuń pliki"
@ -346,8 +398,12 @@
"backup": "Backup", "backup": "Backup",
"config": "Konfiguracja", "config": "Konfiguracja",
"files": "Pliki", "files": "Pliki",
"filter": "Filtruj Logi",
"filterList": "Filtrowane słowa",
"logs": "Logi", "logs": "Logi",
"metrics": "Statystyki",
"playerControls": "Player Management", "playerControls": "Player Management",
"reset": "Resetuj Scrolla",
"schedule": "Harmonogram", "schedule": "Harmonogram",
"serverDetails": "Detale serwera", "serverDetails": "Detale serwera",
"terminal": "Konsola" "terminal": "Konsola"
@ -384,6 +440,11 @@
"waitUpload": "Poczekaj, aż wyślemy twoje pliki... To może chwilkę zająć.", "waitUpload": "Poczekaj, aż wyślemy twoje pliki... To może chwilkę zająć.",
"yesDelete": "Tak, zdaję sobie sprawę z konsekfencji" "yesDelete": "Tak, zdaję sobie sprawę z konsekfencji"
}, },
"serverMetrics": {
"resetZoom": "Zresetuj Przybliżenie",
"zoomHint1": "Aby Przybliżyć statystyki przytrzymaj shifta i użyj kółka na myszy.",
"zoomHint2": "Albo przytrzymaj shifta i zaznacz strefę którą chcesz przybliżyć."
},
"serverPlayerManagement": { "serverPlayerManagement": {
"bannedPlayers": "Zbanowani gracze", "bannedPlayers": "Zbanowani gracze",
"loadingBannedPlayers": "Wczytuję zbanowanych graczy", "loadingBannedPlayers": "Wczytuję zbanowanych graczy",
@ -411,18 +472,36 @@
"parent-explain": "Który harmonogram powinien wykonywać ten?", "parent-explain": "Który harmonogram powinien wykonywać ten?",
"reaction": "Reakcja", "reaction": "Reakcja",
"restart": "Restart Serwera", "restart": "Restart Serwera",
"select": "Basic / Cron / Chain Zaznaczenie reakcji",
"start": "Start Serwera", "start": "Start Serwera",
"stop": "Wyłącz Serwer", "stop": "Wyłącz Serwer",
"time": "Czas", "time": "Czas",
"time-explain": "O jakim czasie ma wykonać się ten harmonogram?" "time-explain": "O jakim czasie ma wykonać się ten harmonogram?"
}, },
"serverSchedules": { "serverSchedules": {
"action": "Akcja",
"areYouSure": "Usuń zaplanowane (zadanie)?", "areYouSure": "Usuń zaplanowane (zadanie)?",
"cancel": "Anuluj", "cancel": "Anuluj",
"cannotSee": "Nie widzisz wszystkiego?", "cannotSee": "Nie widzisz wszystkiego?",
"cannotSeeOnMobile": "Spróbój kliknąć na zadanie dla detali.", "cannotSeeOnMobile": "Spróbój kliknąć na zadanie dla detali.",
"child": "ID dziecka harmonogramu ",
"close": "Zamknij",
"command": "Komenda",
"confirm": "Zapisz", "confirm": "Zapisz",
"confirmDelete": "Czy chcesz usunąć ten zaplanowany task? Jest to nieodwracalne." "confirmDelete": "Czy chcesz usunąć ten zaplanowany task? Jest to nieodwracalne.",
"create": "Utwórz nowy harmonogram",
"cron": "Crong String",
"delete": "Usuń",
"details": "Detale harmonogramu",
"edit": "Edytuj",
"enabled": "Włączony",
"every": "Co",
"interval": "Regularność",
"name": "Nazwa",
"nextRun": "Następne uruchomienie",
"no": "Nie",
"scheduledTasks": "Zaplanowane zadania",
"yes": "Tak"
}, },
"serverStats": { "serverStats": {
"cpuUsage": "Użycie procesora", "cpuUsage": "Użycie procesora",
@ -444,7 +523,8 @@
"serverTerm": { "serverTerm": {
"commandInput": "Wpisz swoją komendę", "commandInput": "Wpisz swoją komendę",
"delay-explained": "Crafty niedawno się odpalił, właczanie serwera będzie trochę opóźnione", "delay-explained": "Crafty niedawno się odpalił, właczanie serwera będzie trochę opóźnione",
"downloading": "Pobieranie...", "importing": "Importowanie...",
"installing": "Instalowanie...",
"restart": "Restart", "restart": "Restart",
"sendCommand": "Wyślij komendę", "sendCommand": "Wyślij komendę",
"start": "Start", "start": "Start",
@ -469,6 +549,7 @@
"importServer": "Importuj egzystujący serwer", "importServer": "Importuj egzystujący serwer",
"importServerButton": "Importuj serwer!", "importServerButton": "Importuj serwer!",
"importZip": "Importuj z ZIPa", "importZip": "Importuj z ZIPa",
"labelZipFile": "Wybierz swój plik ZIP",
"maxMem": "Maks. RAMu", "maxMem": "Maks. RAMu",
"minMem": "Min. RAMu", "minMem": "Min. RAMu",
"myNewServer": "Mój nowy serwer", "myNewServer": "Mój nowy serwer",
@ -478,7 +559,8 @@
"resetForm": "Resetuj formę", "resetForm": "Resetuj formę",
"save": "Zapisz", "save": "Zapisz",
"selectRole": "Zaznacz rolę (lub kilka)", "selectRole": "Zaznacz rolę (lub kilka)",
"selectRoot": "Wybierz archiwum głównej ścieżki | (TBF, Select Archive Root Dir)", "selectRoot": "Wybierz główny katalog archiwa",
"selectServer": "Wybierz Serwer",
"selectType": "Wybierz typ serwera", "selectType": "Wybierz typ serwera",
"selectVersion": "Wybiesz wersje", "selectVersion": "Wybiesz wersje",
"selectZipDir": "Wybierz ścieżkę w archiwóm którą chcesz abyśmy wypakowali", "selectZipDir": "Wybierz ścieżkę w archiwóm którą chcesz abyśmy wypakowali",
@ -486,9 +568,13 @@
"serverName": "Nazwa serwera", "serverName": "Nazwa serwera",
"serverPath": "Ścieżka serwera", "serverPath": "Ścieżka serwera",
"serverPort": "Port serwera", "serverPort": "Port serwera",
"serverSelect": "Wybierz Serwer",
"serverType": "Typ serwera", "serverType": "Typ serwera",
"serverVersion": "Wersja serwera", "serverVersion": "Wersja serwera",
"serverUpload": "Wgraj ZIP Serwera",
"sizeInGB": "Wielkość w GB", "sizeInGB": "Wielkość w GB",
"uploadButton": "Wgraj",
"uploadZip": "Wgraj plik Zip dla imprtowania serwera",
"zipPath": "Server Path" "zipPath": "Server Path"
}, },
"sidebar": { "sidebar": {
@ -496,6 +582,7 @@
"credits": "Podziękowania", "credits": "Podziękowania",
"dashboard": "Panel", "dashboard": "Panel",
"documentation": "Dokumentacja", "documentation": "Dokumentacja",
"inApp": "Dokumentacja w Aplikacji",
"navigation": "Nawigacja", "navigation": "Nawigacja",
"newServer": "Stwórz nowy serwer", "newServer": "Stwórz nowy serwer",
"servers": "Serwery" "servers": "Serwery"
@ -520,6 +607,7 @@
"lastLogin": "Ostatni login: ", "lastLogin": "Ostatni login: ",
"lastUpdate": "Ostatni update: ", "lastUpdate": "Ostatni update: ",
"leaveBlank": "Jeśli chcesz zmienić użytkownika bez zmieniania hasła, nic nie wpisuj.", "leaveBlank": "Jeśli chcesz zmienić użytkownika bez zmieniania hasła, nic nie wpisuj.",
"manager": "Zarządca",
"member": "Użytkownik?", "member": "Użytkownik?",
"notExist": "Nie możesz usunąć czegoś, co nie istnieje!", "notExist": "Nie możesz usunąć czegoś, co nie istnieje!",
"pageTitle": "Edytuj użytkownika", "pageTitle": "Edytuj użytkownika",
@ -528,6 +616,7 @@
"permName": "Nazwa permisji", "permName": "Nazwa permisji",
"repeat": "Powtórz hasło", "repeat": "Powtórz hasło",
"roleName": "Nazwa roli", "roleName": "Nazwa roli",
"selectManager": "Wybierz zarządce dla tej roli",
"super": "SuperUżytkownik", "super": "SuperUżytkownik",
"userLang": "Język użytkownika", "userLang": "Język użytkownika",
"userName": "Nazwa użytkownika", "userName": "Nazwa użytkownika",
@ -535,6 +624,7 @@
"userRoles": "Role użytkownika", "userRoles": "Role użytkownika",
"userRolesDesc": "Role, które ten użytkownik posiada.", "userRolesDesc": "Role, które ten użytkownik posiada.",
"userSettings": "Ustawienia użytkownika", "userSettings": "Ustawienia użytkownika",
"userTheme": "Wygląd UI",
"uses": "Ilość użyć (-1==Bez limitu)" "uses": "Ilość użyć (-1==Bez limitu)"
} }
} }

11
main.py
View File

@ -27,6 +27,17 @@ if helper.check_root():
time.sleep(5) time.sleep(5)
Console.critical("Crafty shutting down. Root/Admin access denied.") Console.critical("Crafty shutting down. Root/Admin access denied.")
sys.exit(0) sys.exit(0)
if not (sys.version_info.major == 3 and sys.version_info.minor >= 9):
Console.critical(
"Python version mismatch. Python "
f"{sys.version_info.major}.{sys.version_info.minor} detected."
)
Console.critical("Crafty requires Python 3.9 or above. Please upgrade python.")
time.sleep(5)
Console.critical("Crafty shutting down.")
time.sleep(3)
Console.info("Crafty stopped. Exiting...")
sys.exit(0)
# pylint: disable=wrong-import-position # pylint: disable=wrong-import-position
try: try:
from app.classes.models.base_model import database_proxy from app.classes.models.base_model import database_proxy

View File

@ -5,17 +5,17 @@ bleach==4.1
cached_property==1.5.2 cached_property==1.5.2
colorama==0.4 colorama==0.4
croniter==1.3.5 croniter==1.3.5
cryptography==39.0.1 cryptography==41.0.1
libgravatar==1.0.0 libgravatar==1.0.0
peewee==3.13 peewee==3.13
pexpect==4.8 pexpect==4.8
psutil==5.9 psutil==5.9
pyOpenSSL==23.0.0 pyOpenSSL==23.2.0
pyjwt==2.4.0 pyjwt==2.4.0
PyYAML==5.4 PyYAML==6.0.1
requests==2.26 requests==2.31
termcolor==1.1 termcolor==1.1
tornado==6.0 tornado==6.3.2
tzlocal==4.0 tzlocal==4.0
jsonschema==4.5.1 jsonschema==4.5.1
orjson==3.6.7 orjson==3.8.12

14
sonar-project.properties Normal file
View File

@ -0,0 +1,14 @@
sonar.projectKey=crafty-controller_crafty-4
sonar.organization=crafty-controller
# This is the name and version displayed in the SonarCloud UI.
sonar.projectName=Crafty 4
sonar.projectVersion=4.1.4
sonar.python.version=3.9, 3.10, 3.11
sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
#sonar.sources=.
# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8