mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into feature/steamcmd
This commit is contained in:
commit
d8ddc1ce27
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,6 +18,7 @@ env.bak/
|
|||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
/imports/
|
||||||
/servers/
|
/servers/
|
||||||
/backups/
|
/backups/
|
||||||
/temp/
|
/temp/
|
||||||
|
@ -51,20 +51,9 @@ pylint:
|
|||||||
- if: "$CODE_QUALITY_DISABLED"
|
- if: "$CODE_QUALITY_DISABLED"
|
||||||
when: never
|
when: never
|
||||||
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
|
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
|
||||||
before_script:
|
|
||||||
- mkdir -p public/badges public/lint
|
|
||||||
- echo undefined > public/badges/$CI_JOB_NAME.score
|
|
||||||
script:
|
script:
|
||||||
- pylint --exit-zero --output-format=text $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**") | tee /tmp/pylint.txt
|
- pylint --exit-zero --load-plugins=pylint_gitlab --output-format=gitlab-codeclimate:codeclimate.json $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**")
|
||||||
- sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' /tmp/pylint.txt > public/badges/$CI_JOB_NAME.score
|
|
||||||
- pylint --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**") > codeclimate.json
|
|
||||||
after_script:
|
|
||||||
- anybadge --overwrite --label $CI_JOB_NAME --value=$(cat public/badges/$CI_JOB_NAME.score) --file=public/badges/$CI_JOB_NAME.svg 4=red 6=orange 8=yellow 10=green
|
|
||||||
- |
|
|
||||||
echo "Your score is: $(cat public/badges/$CI_JOB_NAME.score)"
|
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
|
||||||
- public
|
|
||||||
reports:
|
reports:
|
||||||
codequality: codeclimate.json
|
codequality: codeclimate.json
|
||||||
when: always
|
when: always
|
||||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@ -1,5 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## --- [4.0.13] - 2022/TBD
|
## --- [4.0.16] - 2022/TBD
|
||||||
### New features
|
### New features
|
||||||
TBD
|
TBD
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
@ -10,6 +10,44 @@ TBD
|
|||||||
TBD
|
TBD
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
## --- [4.0.15] - 2022/10/02
|
||||||
|
### New features
|
||||||
|
- Base Theme Switching (Dark, Light, Default) 🤩🎨 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/471))
|
||||||
|
- Upload Zip functionality for server imports 🏗️🎉 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/472))
|
||||||
|
### Bug fixes
|
||||||
|
- Fix traceback on basic schedule with "days" interval ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/469))
|
||||||
|
- Fix bad method call with API stdin ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/470))<br>
|
||||||
|
*(Thank you ['IWant2Tryhard'](https://github.com/MyNameTsThad) for catching that 🐛)*
|
||||||
|
- Fix clients variable as static to prevent crash if client list changed while sending a websocket ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/473))
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## --- [4.0.14] - 2022/09/23
|
||||||
|
### Bug fixes
|
||||||
|
- HOTFIX - Rollback breaking websockets change !461 (self.clients was already a set and we tried to subscript a set of a set) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/467))
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## --- [4.0.13] - 2022/09/20
|
||||||
|
### Bug fixes
|
||||||
|
- Fix bug where trying to reconfigure unloaded server would stack ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/1b2fef06fb3b02b76c9506caf7e07e932df95fab) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/460))
|
||||||
|
- Fix traceback error when a user click the roles config tab while already on the roles config page; **this is for new role creation only** ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/452))
|
||||||
|
- Fix logic issue when removing items from backup exclusions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/453))
|
||||||
|
- Cleanup various JS errors ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/455))
|
||||||
|
- Temp fix for `&` issue in pathing and minecraft colour codes ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/457))
|
||||||
|
- Cache Gravatar pfp's as to not query every page load ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/459))
|
||||||
|
- Fix crash on client list changing while sending websockets ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/461))
|
||||||
|
- Set default parent option on edit of reaction schedule ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/462))
|
||||||
|
- Fix wtol Nonetype error on server start when 'which java' returns `none` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/463))
|
||||||
|
### Tweaks
|
||||||
|
- Add button to scroll to bottom of vterm ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/454))
|
||||||
|
- Persist schedules and execution commands across backup restores ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/458))
|
||||||
|
### Release Testing- Bug fixes
|
||||||
|
- Fix bug with logical issues surrounding gravatar caching ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/465))
|
||||||
|
- Fix bug where server terminal would not scroll on startup ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/465))
|
||||||
|
- Fix issue on post with adding users when no email is included (this also affected editing users) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/466))
|
||||||
|
- Fix issue with schedules allowing days to be more than 30 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/466))
|
||||||
|
- Fix issue with schedules when trying to edit a cron task ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/466))
|
||||||
|
<br><br>
|
||||||
|
|
||||||
## --- [4.0.12] - 2022/09/04
|
## --- [4.0.12] - 2022/09/04
|
||||||
### New features
|
### New features
|
||||||
- Win Portable Updater will now be included in Windows Package ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/446))
|
- Win Portable Updater will now be included in Windows Package ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/446))
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
|
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
|
||||||
[![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org)
|
[![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org)
|
||||||
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.13--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
|
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.16--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
|
||||||
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4)
|
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4)
|
||||||
[![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master)
|
[![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master)
|
||||||
|
|
||||||
# Crafty Controller 4.0.13-beta
|
# Crafty Controller 4.0.15-beta
|
||||||
> Python based Control Panel for your Minecraft Server
|
> Python based Control Panel for your Minecraft Server
|
||||||
|
|
||||||
## What is Crafty Controller?
|
## What is Crafty Controller?
|
||||||
|
@ -4,7 +4,6 @@ import time
|
|||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
import typing as t
|
import typing as t
|
||||||
import datetime
|
|
||||||
|
|
||||||
from app.classes.controllers.roles_controller import RolesController
|
from app.classes.controllers.roles_controller import RolesController
|
||||||
from app.classes.shared.file_helpers import FileHelpers
|
from app.classes.shared.file_helpers import FileHelpers
|
||||||
|
@ -147,14 +147,28 @@ class UsersController:
|
|||||||
return HelperServers.get_total_owned_servers(exec_user_id)
|
return HelperServers.get_total_owned_servers(exec_user_id)
|
||||||
|
|
||||||
def update_user(self, user_id: str, user_data=None, user_crafty_data=None):
|
def update_user(self, user_id: str, user_data=None, user_crafty_data=None):
|
||||||
|
# check if user crafty perms were updated
|
||||||
if user_crafty_data is None:
|
if user_crafty_data is None:
|
||||||
user_crafty_data = {}
|
user_crafty_data = {}
|
||||||
|
# check if general user data was updated
|
||||||
if user_data is None:
|
if user_data is None:
|
||||||
user_data = {}
|
user_data = {}
|
||||||
|
# get current user data
|
||||||
base_data = HelperUsers.get_user(user_id)
|
base_data = HelperUsers.get_user(user_id)
|
||||||
up_data = {}
|
up_data = {}
|
||||||
|
# check if we updated user email. If so we update gravatar
|
||||||
|
try:
|
||||||
|
if user_data["email"] != base_data["email"]:
|
||||||
|
pfp = self.helper.get_gravatar_image(user_data["email"])
|
||||||
|
up_data["pfp"] = pfp
|
||||||
|
except KeyError:
|
||||||
|
logger.debug("Email not updated")
|
||||||
|
# email not updated
|
||||||
|
# create sets to store role data
|
||||||
added_roles = set()
|
added_roles = set()
|
||||||
removed_roles = set()
|
removed_roles = set()
|
||||||
|
|
||||||
|
# search for changes in user data
|
||||||
for key in user_data:
|
for key in user_data:
|
||||||
if key == "user_id":
|
if key == "user_id":
|
||||||
continue
|
continue
|
||||||
@ -174,8 +188,10 @@ class UsersController:
|
|||||||
up_data["hints"] = user_data["hints"]
|
up_data["hints"] = user_data["hints"]
|
||||||
elif base_data[key] != user_data[key]:
|
elif base_data[key] != user_data[key]:
|
||||||
up_data[key] = user_data[key]
|
up_data[key] = user_data[key]
|
||||||
|
# change last update for user
|
||||||
up_data["last_update"] = self.helper.get_time_as_string()
|
up_data["last_update"] = self.helper.get_time_as_string()
|
||||||
logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}")
|
logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}")
|
||||||
|
|
||||||
for role in added_roles:
|
for role in added_roles:
|
||||||
HelperUsers.get_or_create(user_id=user_id, role_id=role)
|
HelperUsers.get_or_create(user_id=user_id, role_id=role)
|
||||||
permissions_mask = user_crafty_data.get("permissions_mask", "000")
|
permissions_mask = user_crafty_data.get("permissions_mask", "000")
|
||||||
@ -225,6 +241,7 @@ class UsersController:
|
|||||||
email="default@example.com",
|
email="default@example.com",
|
||||||
enabled: bool = True,
|
enabled: bool = True,
|
||||||
superuser: bool = False,
|
superuser: bool = False,
|
||||||
|
theme="default",
|
||||||
):
|
):
|
||||||
return self.users_helper.add_user(
|
return self.users_helper.add_user(
|
||||||
username,
|
username,
|
||||||
@ -233,6 +250,7 @@ class UsersController:
|
|||||||
email=email,
|
email=email,
|
||||||
enabled=enabled,
|
enabled=enabled,
|
||||||
superuser=superuser,
|
superuser=superuser,
|
||||||
|
theme=theme,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -42,6 +42,8 @@ class Users(BaseModel):
|
|||||||
preparing = BooleanField(default=False)
|
preparing = BooleanField(default=False)
|
||||||
hints = BooleanField(default=True)
|
hints = BooleanField(default=True)
|
||||||
manager = IntegerField(default=None, null=True)
|
manager = IntegerField(default=None, null=True)
|
||||||
|
pfp = CharField(default="/static/assets/images/faces-clipart/pic-3.png")
|
||||||
|
theme = CharField(default="default")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table_name = "users"
|
table_name = "users"
|
||||||
@ -209,6 +211,7 @@ class HelperUsers:
|
|||||||
email: t.Optional[str] = None,
|
email: t.Optional[str] = None,
|
||||||
enabled: bool = True,
|
enabled: bool = True,
|
||||||
superuser: bool = False,
|
superuser: bool = False,
|
||||||
|
theme: str = "default",
|
||||||
) -> str:
|
) -> str:
|
||||||
if password is not None:
|
if password is not None:
|
||||||
pw_enc = self.helper.encode_pass(password)
|
pw_enc = self.helper.encode_pass(password)
|
||||||
@ -220,9 +223,11 @@ class HelperUsers:
|
|||||||
Users.password: pw_enc,
|
Users.password: pw_enc,
|
||||||
Users.email: email,
|
Users.email: email,
|
||||||
Users.enabled: enabled,
|
Users.enabled: enabled,
|
||||||
|
Users.pfp: self.helper.get_gravatar_image(email),
|
||||||
Users.superuser: superuser,
|
Users.superuser: superuser,
|
||||||
Users.created: Helpers.get_time_as_string(),
|
Users.created: Helpers.get_time_as_string(),
|
||||||
Users.manager: manager,
|
Users.manager: manager,
|
||||||
|
Users.theme: theme,
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
return user_id
|
return user_id
|
||||||
|
@ -226,18 +226,24 @@ class FileHelpers:
|
|||||||
comment, "utf-8"
|
comment, "utf-8"
|
||||||
) # comments over 65535 bytes will be truncated
|
) # comments over 65535 bytes will be truncated
|
||||||
for root, dirs, files in os.walk(path_to_zip, topdown=True):
|
for root, dirs, files in os.walk(path_to_zip, topdown=True):
|
||||||
for l_dir in dirs:
|
for l_dir in dirs[:]:
|
||||||
|
# make all paths in exclusions a unix style slash
|
||||||
|
# to match directories.
|
||||||
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
|
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
|
||||||
dirs.remove(l_dir)
|
dirs.remove(l_dir)
|
||||||
ziproot = path_to_zip
|
ziproot = path_to_zip
|
||||||
|
# iterate through list of files
|
||||||
for file in files:
|
for file in files:
|
||||||
|
# check if file/dir is in exclusions list.
|
||||||
|
# Only proceed if not exluded.
|
||||||
if (
|
if (
|
||||||
str(os.path.join(root, file)).replace("\\", "/")
|
str(os.path.join(root, file)).replace("\\", "/")
|
||||||
not in ex_replace
|
not in ex_replace
|
||||||
and file != "crafty.sqlite"
|
and file != "crafty.sqlite"
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
logger.info(f"backing up: {os.path.join(root, file)}")
|
logger.debug(f"backing up: {os.path.join(root, file)}")
|
||||||
|
# add trailing slash to zip root dir if not windows.
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
zip_file.write(
|
zip_file.write(
|
||||||
os.path.join(root, file),
|
os.path.join(root, file),
|
||||||
@ -254,12 +260,20 @@ class FileHelpers:
|
|||||||
f"Error backing up: {os.path.join(root, file)}!"
|
f"Error backing up: {os.path.join(root, file)}!"
|
||||||
f" - Error was: {e}"
|
f" - Error was: {e}"
|
||||||
)
|
)
|
||||||
|
# debug logging for exlusions list
|
||||||
|
else:
|
||||||
|
logger.debug(f"Found {file} in exclusion list. Skipping...")
|
||||||
|
|
||||||
|
# add current file bytes to total bytes.
|
||||||
total_bytes += os.path.getsize(os.path.join(root, file))
|
total_bytes += os.path.getsize(os.path.join(root, file))
|
||||||
|
# calcualte percentage based off total size and current archive size
|
||||||
percent = round((total_bytes / dir_bytes) * 100, 2)
|
percent = round((total_bytes / dir_bytes) * 100, 2)
|
||||||
|
# package results
|
||||||
results = {
|
results = {
|
||||||
"percent": percent,
|
"percent": percent,
|
||||||
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||||
}
|
}
|
||||||
|
# send status results to page.
|
||||||
self.helper.websocket_helper.broadcast_page_params(
|
self.helper.websocket_helper.broadcast_page_params(
|
||||||
"/panel/server_detail",
|
"/panel/server_detail",
|
||||||
{"id": str(server_id)},
|
{"id": str(server_id)},
|
||||||
|
@ -20,6 +20,7 @@ import itertools
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
from contextlib import redirect_stderr, suppress
|
from contextlib import redirect_stderr, suppress
|
||||||
|
import libgravatar
|
||||||
from packaging import version as pkg_version
|
from packaging import version as pkg_version
|
||||||
|
|
||||||
from app.classes.shared.null_writer import NullWriter
|
from app.classes.shared.null_writer import NullWriter
|
||||||
@ -399,6 +400,10 @@ class Helpers:
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_themes():
|
||||||
|
return ["default", "dark", "light", "ronald"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_local_ip():
|
def get_local_ip():
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
@ -659,6 +664,33 @@ class Helpers:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_gravatar_image(self, email):
|
||||||
|
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
||||||
|
# http://en.gravatar.com/site/implement/images/#rating
|
||||||
|
if self.get_setting("allow_nsfw_profile_pictures"):
|
||||||
|
rating = "x"
|
||||||
|
else:
|
||||||
|
rating = "g"
|
||||||
|
|
||||||
|
# Get grvatar hash for profile pictures
|
||||||
|
if self.check_internet() and email != "default@example.com" and email:
|
||||||
|
gravatar = libgravatar.Gravatar(libgravatar.sanitize_email(email))
|
||||||
|
url = gravatar.get_image(
|
||||||
|
size=80,
|
||||||
|
default="404",
|
||||||
|
force_default=False,
|
||||||
|
rating=rating,
|
||||||
|
filetype_extension=False,
|
||||||
|
use_ssl=True,
|
||||||
|
) # + "?d=404"
|
||||||
|
try:
|
||||||
|
if requests.head(url).status_code != 404:
|
||||||
|
profile_url = url
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Could not pull resource from Gravatar with error {e}")
|
||||||
|
|
||||||
|
return profile_url
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_file_contents(path: str, lines=100):
|
def get_file_contents(path: str, lines=100):
|
||||||
|
|
||||||
|
@ -242,32 +242,36 @@ class ServerInstance:
|
|||||||
"Detected nebulous java in start command. "
|
"Detected nebulous java in start command. "
|
||||||
"Replacing with full java path."
|
"Replacing with full java path."
|
||||||
)
|
)
|
||||||
# Checks for Oracle Java. Only Oracle Java's helper will cause a re-exec.
|
oracle_path = shutil.which("java")
|
||||||
if "/Oracle/Java/" in str(self.helper.wtol_path(shutil.which("java"))):
|
if oracle_path:
|
||||||
logger.info(
|
# Checks for Oracle Java. Only Oracle Java's helper will cause a re-exec
|
||||||
"Oracle Java detected. Changing start command to avoid re-exec."
|
if "/Oracle/Java/" in str(self.helper.wtol_path(oracle_path)):
|
||||||
)
|
logger.info(
|
||||||
which_java_raw = self.helper.which_java()
|
"Oracle Java detected. Changing"
|
||||||
try:
|
" start command to avoid re-exec."
|
||||||
java_path = which_java_raw + "\\bin\\java"
|
|
||||||
except TypeError:
|
|
||||||
logger.warning(
|
|
||||||
"Could not find java in the registry even though"
|
|
||||||
" Oracle java is installed. Re-exec expected, but we have no"
|
|
||||||
" other options. CPU stats will not work for process."
|
|
||||||
)
|
)
|
||||||
java_path = ""
|
which_java_raw = self.helper.which_java()
|
||||||
if str(which_java_raw) != str(self.helper.get_servers_root_dir) or str(
|
try:
|
||||||
self.helper.get_servers_root_dir
|
java_path = which_java_raw + "\\bin\\java"
|
||||||
) in str(which_java_raw):
|
except TypeError:
|
||||||
if java_path != "":
|
logger.warning(
|
||||||
self.server_command[0] = java_path
|
"Could not find java in the registry even though"
|
||||||
else:
|
" Oracle java is installed."
|
||||||
logger.critcal(
|
" Re-exec expected, but we have no"
|
||||||
"Possible attack detected. User attempted to exec "
|
" other options. CPU stats will not work for process."
|
||||||
"java binary from server directory."
|
)
|
||||||
)
|
java_path = ""
|
||||||
return
|
if str(which_java_raw) != str(
|
||||||
|
self.helper.get_servers_root_dir
|
||||||
|
) or str(self.helper.get_servers_root_dir) in str(which_java_raw):
|
||||||
|
if java_path != "":
|
||||||
|
self.server_command[0] = java_path
|
||||||
|
else:
|
||||||
|
logger.critcal(
|
||||||
|
"Possible attack detected. User attempted to exec "
|
||||||
|
"java binary from server directory."
|
||||||
|
)
|
||||||
|
return
|
||||||
self.server_path = Helpers.get_os_understandable_path(self.settings["path"])
|
self.server_path = Helpers.get_os_understandable_path(self.settings["path"])
|
||||||
|
|
||||||
# let's do some quick checking to make sure things actually exists
|
# let's do some quick checking to make sure things actually exists
|
||||||
|
@ -678,6 +678,24 @@ class TasksManager:
|
|||||||
logger.info(
|
logger.info(
|
||||||
"No updates found! You are on the most up to date Crafty version."
|
"No updates found! You are on the most up to date Crafty version."
|
||||||
)
|
)
|
||||||
|
logger.info("Refreshing Gravatar PFPs...")
|
||||||
|
for user in HelperUsers.get_all_users():
|
||||||
|
if user.email:
|
||||||
|
HelperUsers.update_user(
|
||||||
|
user.id, {"pfp": self.helper.get_gravatar_image(user.email)}
|
||||||
|
)
|
||||||
|
# Search for old files in imports
|
||||||
|
self.helper.ensure_dir_exists(
|
||||||
|
os.path.join(self.controller.project_root, "imports")
|
||||||
|
)
|
||||||
|
for file in os.listdir(os.path.join(self.controller.project_root, "imports")):
|
||||||
|
if self.helper.is_file_older_than_x_days(
|
||||||
|
os.path.join(self.controller.project_root, "imports", file)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(file))
|
||||||
|
except:
|
||||||
|
logger.debug("Could not clear out file from import directory")
|
||||||
|
|
||||||
def log_watcher(self):
|
def log_watcher(self):
|
||||||
self.controller.servers.check_for_old_logs()
|
self.controller.servers.check_for_old_logs()
|
||||||
|
@ -383,6 +383,8 @@ class AjaxHandler(BaseHandler):
|
|||||||
zip_name = bleach.clean(self.get_argument("zip_file", None))
|
zip_name = bleach.clean(self.get_argument("zip_file", None))
|
||||||
svr_obj = self.controller.servers.get_server_obj(server_id)
|
svr_obj = self.controller.servers.get_server_obj(server_id)
|
||||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
||||||
|
|
||||||
|
# import the server again based on zipfile
|
||||||
if server_data["type"] == "minecraft-java":
|
if server_data["type"] == "minecraft-java":
|
||||||
backup_path = svr_obj.backup_path
|
backup_path = svr_obj.backup_path
|
||||||
if Helpers.validate_traversal(backup_path, zip_name):
|
if Helpers.validate_traversal(backup_path, zip_name):
|
||||||
@ -401,6 +403,27 @@ class AjaxHandler(BaseHandler):
|
|||||||
self.controller.rename_backup_dir(
|
self.controller.rename_backup_dir(
|
||||||
server_id, new_server_id, new_server["server_uuid"]
|
server_id, new_server_id, new_server["server_uuid"]
|
||||||
)
|
)
|
||||||
|
# preserve current schedules
|
||||||
|
for schedule in self.controller.management.get_schedules_by_server(
|
||||||
|
server_id
|
||||||
|
):
|
||||||
|
self.controller.management.create_scheduled_task(
|
||||||
|
new_server_id,
|
||||||
|
schedule.action,
|
||||||
|
schedule.interval,
|
||||||
|
schedule.interval_type,
|
||||||
|
schedule.start_time,
|
||||||
|
schedule.command,
|
||||||
|
schedule.name,
|
||||||
|
schedule.enabled,
|
||||||
|
)
|
||||||
|
# preserve execution command
|
||||||
|
new_server_obj = self.controller.servers.get_server_obj(
|
||||||
|
new_server_id
|
||||||
|
)
|
||||||
|
new_server_obj.execution_command = server_data["execution_command"]
|
||||||
|
self.controller.servers.update_server(new_server_obj)
|
||||||
|
# remove old server's tasks
|
||||||
try:
|
try:
|
||||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||||
except:
|
except:
|
||||||
@ -424,6 +447,26 @@ class AjaxHandler(BaseHandler):
|
|||||||
self.controller.rename_backup_dir(
|
self.controller.rename_backup_dir(
|
||||||
server_id, new_server_id, new_server["server_uuid"]
|
server_id, new_server_id, new_server["server_uuid"]
|
||||||
)
|
)
|
||||||
|
# preserve current schedules
|
||||||
|
for schedule in self.controller.management.get_schedules_by_server(
|
||||||
|
server_id
|
||||||
|
):
|
||||||
|
self.controller.management.create_scheduled_task(
|
||||||
|
new_server_id,
|
||||||
|
schedule.action,
|
||||||
|
schedule.interval,
|
||||||
|
schedule.interval_type,
|
||||||
|
schedule.start_time,
|
||||||
|
schedule.command,
|
||||||
|
schedule.name,
|
||||||
|
schedule.enabled,
|
||||||
|
)
|
||||||
|
# preserve execution command
|
||||||
|
new_server_obj = self.controller.servers.get_server_obj(
|
||||||
|
new_server_id
|
||||||
|
)
|
||||||
|
new_server_obj.execution_command = server_data["execution_command"]
|
||||||
|
self.controller.servers.update_server(new_server_obj)
|
||||||
try:
|
try:
|
||||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||||
except:
|
except:
|
||||||
@ -433,6 +476,12 @@ class AjaxHandler(BaseHandler):
|
|||||||
|
|
||||||
elif page == "unzip_server":
|
elif page == "unzip_server":
|
||||||
path = self.get_argument("path", None)
|
path = self.get_argument("path", None)
|
||||||
|
if not path:
|
||||||
|
path = os.path.join(
|
||||||
|
self.controller.project_root,
|
||||||
|
"imports",
|
||||||
|
self.get_argument("file", ""),
|
||||||
|
)
|
||||||
if Helpers.check_file_exists(path):
|
if Helpers.check_file_exists(path):
|
||||||
self.helper.unzip_server(path, exec_user["user_id"])
|
self.helper.unzip_server(path, exec_user["user_id"])
|
||||||
else:
|
else:
|
||||||
|
@ -104,7 +104,10 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||||||
strip: bool = True,
|
strip: bool = True,
|
||||||
) -> t.Optional[str]:
|
) -> t.Optional[str]:
|
||||||
arg = self._get_argument(name, default, self.request.arguments, strip)
|
arg = self._get_argument(name, default, self.request.arguments, strip)
|
||||||
return self.autobleach(name, arg)
|
bleached = self.autobleach(name, arg)
|
||||||
|
if "&" in str(bleached):
|
||||||
|
bleached = bleached.replace("&", "&")
|
||||||
|
return bleached
|
||||||
|
|
||||||
def get_arguments(self, name: str, strip: bool = True) -> t.List[str]:
|
def get_arguments(self, name: str, strip: bool = True) -> t.List[str]:
|
||||||
if not isinstance(strip, bool):
|
if not isinstance(strip, bool):
|
||||||
|
@ -8,7 +8,6 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import shlex
|
import shlex
|
||||||
import bleach
|
import bleach
|
||||||
import libgravatar
|
|
||||||
import requests
|
import requests
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.escape
|
import tornado.escape
|
||||||
@ -331,37 +330,6 @@ class PanelHandler(BaseHandler):
|
|||||||
"superuser": superuser,
|
"superuser": superuser,
|
||||||
}
|
}
|
||||||
|
|
||||||
# http://en.gravatar.com/site/implement/images/#rating
|
|
||||||
if self.helper.get_setting("allow_nsfw_profile_pictures"):
|
|
||||||
rating = "x"
|
|
||||||
else:
|
|
||||||
rating = "g"
|
|
||||||
|
|
||||||
# Get grvatar hash for profile pictures
|
|
||||||
if exec_user["email"] != "default@example.com" or "":
|
|
||||||
gravatar = libgravatar.Gravatar(
|
|
||||||
libgravatar.sanitize_email(exec_user["email"])
|
|
||||||
)
|
|
||||||
url = gravatar.get_image(
|
|
||||||
size=80,
|
|
||||||
default="404",
|
|
||||||
force_default=False,
|
|
||||||
rating=rating,
|
|
||||||
filetype_extension=False,
|
|
||||||
use_ssl=True,
|
|
||||||
) # + "?d=404"
|
|
||||||
try:
|
|
||||||
if requests.head(url).status_code != 404:
|
|
||||||
profile_url = url
|
|
||||||
else:
|
|
||||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
|
||||||
except:
|
|
||||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
|
||||||
else:
|
|
||||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
|
||||||
|
|
||||||
page_data["user_image"] = profile_url
|
|
||||||
|
|
||||||
if page == "unauthorized":
|
if page == "unauthorized":
|
||||||
template = "panel/denied.html"
|
template = "panel/denied.html"
|
||||||
|
|
||||||
@ -549,7 +517,7 @@ class PanelHandler(BaseHandler):
|
|||||||
"log_path": server_temp_obj["log_path"],
|
"log_path": server_temp_obj["log_path"],
|
||||||
"executable": server_temp_obj["executable"],
|
"executable": server_temp_obj["executable"],
|
||||||
"execution_command": server_temp_obj["execution_command"],
|
"execution_command": server_temp_obj["execution_command"],
|
||||||
"shutdown_timeout": server_obj["shutdown_timeout"],
|
"shutdown_timeout": server_temp_obj["shutdown_timeout"],
|
||||||
"stop_command": server_temp_obj["stop_command"],
|
"stop_command": server_temp_obj["stop_command"],
|
||||||
"executable_update_url": server_temp_obj[
|
"executable_update_url": server_temp_obj[
|
||||||
"executable_update_url"
|
"executable_update_url"
|
||||||
@ -910,6 +878,7 @@ class PanelHandler(BaseHandler):
|
|||||||
page_data["user"]["roles"] = set()
|
page_data["user"]["roles"] = set()
|
||||||
page_data["user"]["hints"] = True
|
page_data["user"]["hints"] = True
|
||||||
page_data["superuser"] = superuser
|
page_data["superuser"] = superuser
|
||||||
|
page_data["themes"] = self.helper.get_themes()
|
||||||
|
|
||||||
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
|
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
|
||||||
self.redirect(
|
self.redirect(
|
||||||
@ -1009,6 +978,7 @@ class PanelHandler(BaseHandler):
|
|||||||
# 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"
|
||||||
page_data["schedule"]["interval_type"] = "days"
|
page_data["schedule"]["interval_type"] = "days"
|
||||||
|
page_data["parent"] = None
|
||||||
|
|
||||||
if not EnumPermissionsServer.SCHEDULE in page_data["user_permissions"]:
|
if not EnumPermissionsServer.SCHEDULE in page_data["user_permissions"]:
|
||||||
if not superuser:
|
if not superuser:
|
||||||
@ -1091,10 +1061,15 @@ class PanelHandler(BaseHandler):
|
|||||||
page_data["schedule"]["interval_type"] = schedule.interval_type
|
page_data["schedule"]["interval_type"] = schedule.interval_type
|
||||||
if schedule.interval_type == "reaction":
|
if schedule.interval_type == "reaction":
|
||||||
difficulty = "reaction"
|
difficulty = "reaction"
|
||||||
|
page_data["parent"] = self.controller.management.get_scheduled_task(
|
||||||
|
schedule.parent
|
||||||
|
)
|
||||||
elif schedule.cron_string == "":
|
elif schedule.cron_string == "":
|
||||||
difficulty = "basic"
|
difficulty = "basic"
|
||||||
|
page_data["parent"] = None
|
||||||
else:
|
else:
|
||||||
difficulty = "advanced"
|
difficulty = "advanced"
|
||||||
|
page_data["parent"] = None
|
||||||
page_data["schedule"]["difficulty"] = difficulty
|
page_data["schedule"]["difficulty"] = difficulty
|
||||||
|
|
||||||
if not EnumPermissionsServer.SCHEDULE in page_data["user_permissions"]:
|
if not EnumPermissionsServer.SCHEDULE in page_data["user_permissions"]:
|
||||||
@ -1118,6 +1093,7 @@ class PanelHandler(BaseHandler):
|
|||||||
page_data["exec_user"] = exec_user["user_id"]
|
page_data["exec_user"] = exec_user["user_id"]
|
||||||
page_data["servers_all"] = self.controller.servers.get_all_defined_servers()
|
page_data["servers_all"] = self.controller.servers.get_all_defined_servers()
|
||||||
page_data["superuser"] = superuser
|
page_data["superuser"] = superuser
|
||||||
|
page_data["themes"] = self.helper.get_themes()
|
||||||
if page_data["user"]["manager"] is not None:
|
if page_data["user"]["manager"] is not None:
|
||||||
page_data["manager"] = self.controller.users.get_user_by_id(
|
page_data["manager"] = self.controller.users.get_user_by_id(
|
||||||
page_data["user"]["manager"]
|
page_data["user"]["manager"]
|
||||||
@ -1726,8 +1702,14 @@ class PanelHandler(BaseHandler):
|
|||||||
# only check for time if it's number of days
|
# only check for time if it's number of days
|
||||||
if interval_type == "days":
|
if interval_type == "days":
|
||||||
sch_time = bleach.clean(self.get_argument("time", None))
|
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":
|
if action == "command":
|
||||||
command = bleach.clean(self.get_argument("command", None))
|
command = self.get_argument("command", None)
|
||||||
elif action == "start":
|
elif action == "start":
|
||||||
command = "start_server"
|
command = "start_server"
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
@ -1743,7 +1725,7 @@ class PanelHandler(BaseHandler):
|
|||||||
delay = bleach.clean(self.get_argument("delay", None))
|
delay = bleach.clean(self.get_argument("delay", None))
|
||||||
parent = bleach.clean(self.get_argument("parent", None))
|
parent = bleach.clean(self.get_argument("parent", None))
|
||||||
if action == "command":
|
if action == "command":
|
||||||
command = bleach.clean(self.get_argument("command", None))
|
command = self.get_argument("command", None)
|
||||||
elif action == "start":
|
elif action == "start":
|
||||||
command = "start_server"
|
command = "start_server"
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
@ -1763,7 +1745,7 @@ class PanelHandler(BaseHandler):
|
|||||||
return
|
return
|
||||||
action = bleach.clean(self.get_argument("action", None))
|
action = bleach.clean(self.get_argument("action", None))
|
||||||
if action == "command":
|
if action == "command":
|
||||||
command = bleach.clean(self.get_argument("command", None))
|
command = self.get_argument("command", None)
|
||||||
elif action == "start":
|
elif action == "start":
|
||||||
command = "start_server"
|
command = "start_server"
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
@ -1888,8 +1870,14 @@ class PanelHandler(BaseHandler):
|
|||||||
# only check for time if it's number of days
|
# only check for time if it's number of days
|
||||||
if interval_type == "days":
|
if interval_type == "days":
|
||||||
sch_time = bleach.clean(self.get_argument("time", None))
|
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":
|
if action == "command":
|
||||||
command = bleach.clean(self.get_argument("command", None))
|
command = self.get_argument("command", None)
|
||||||
elif action == "start":
|
elif action == "start":
|
||||||
command = "start_server"
|
command = "start_server"
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
@ -1904,7 +1892,7 @@ class PanelHandler(BaseHandler):
|
|||||||
delay = bleach.clean(self.get_argument("delay", None))
|
delay = bleach.clean(self.get_argument("delay", None))
|
||||||
parent = bleach.clean(self.get_argument("parent", None))
|
parent = bleach.clean(self.get_argument("parent", None))
|
||||||
if action == "command":
|
if action == "command":
|
||||||
command = bleach.clean(self.get_argument("command", None))
|
command = self.get_argument("command", None)
|
||||||
elif action == "start":
|
elif action == "start":
|
||||||
command = "start_server"
|
command = "start_server"
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
@ -1924,7 +1912,7 @@ class PanelHandler(BaseHandler):
|
|||||||
return
|
return
|
||||||
action = bleach.clean(self.get_argument("action", None))
|
action = bleach.clean(self.get_argument("action", None))
|
||||||
if action == "command":
|
if action == "command":
|
||||||
command = bleach.clean(self.get_argument("command", None))
|
command = self.get_argument("command", None)
|
||||||
elif action == "start":
|
elif action == "start":
|
||||||
command = "start_server"
|
command = "start_server"
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
@ -2025,6 +2013,7 @@ class PanelHandler(BaseHandler):
|
|||||||
user_id = bleach.clean(self.get_argument("id", None))
|
user_id = bleach.clean(self.get_argument("id", None))
|
||||||
user = self.controller.users.get_user_by_id(user_id)
|
user = self.controller.users.get_user_by_id(user_id)
|
||||||
username = bleach.clean(self.get_argument("username", None).lower())
|
username = bleach.clean(self.get_argument("username", None).lower())
|
||||||
|
theme = bleach.clean(self.get_argument("theme", "default"))
|
||||||
if (
|
if (
|
||||||
username != self.controller.users.get_user_by_id(user_id)["username"]
|
username != self.controller.users.get_user_by_id(user_id)["username"]
|
||||||
and username in self.controller.users.get_all_usernames()
|
and username in self.controller.users.get_all_usernames()
|
||||||
@ -2091,6 +2080,7 @@ class PanelHandler(BaseHandler):
|
|||||||
"email": email,
|
"email": email,
|
||||||
"lang": lang,
|
"lang": lang,
|
||||||
"hints": hints,
|
"hints": hints,
|
||||||
|
"theme": theme,
|
||||||
}
|
}
|
||||||
self.controller.users.update_user(user_id, user_data=user_data)
|
self.controller.users.update_user(user_id, user_data=user_data)
|
||||||
|
|
||||||
@ -2127,6 +2117,7 @@ class PanelHandler(BaseHandler):
|
|||||||
"lang": lang,
|
"lang": lang,
|
||||||
"superuser": superuser,
|
"superuser": superuser,
|
||||||
"hints": hints,
|
"hints": hints,
|
||||||
|
"theme": theme,
|
||||||
}
|
}
|
||||||
user_crafty_data = {
|
user_crafty_data = {
|
||||||
"permissions_mask": permissions_mask,
|
"permissions_mask": permissions_mask,
|
||||||
@ -2238,6 +2229,7 @@ class PanelHandler(BaseHandler):
|
|||||||
password1 = bleach.clean(self.get_argument("password1", None))
|
password1 = bleach.clean(self.get_argument("password1", None))
|
||||||
email = bleach.clean(self.get_argument("email", "default@example.com"))
|
email = bleach.clean(self.get_argument("email", "default@example.com"))
|
||||||
enabled = int(float(self.get_argument("enabled", "0")))
|
enabled = int(float(self.get_argument("enabled", "0")))
|
||||||
|
theme = bleach.clean(self.get_argument("theme"), "default")
|
||||||
hints = True
|
hints = True
|
||||||
lang = bleach.clean(
|
lang = bleach.clean(
|
||||||
self.get_argument("lang", self.helper.get_setting("language"))
|
self.get_argument("lang", self.helper.get_setting("language"))
|
||||||
@ -2293,6 +2285,7 @@ class PanelHandler(BaseHandler):
|
|||||||
email=email,
|
email=email,
|
||||||
enabled=enabled,
|
enabled=enabled,
|
||||||
superuser=new_superuser,
|
superuser=new_superuser,
|
||||||
|
theme=theme,
|
||||||
)
|
)
|
||||||
user_data = {"roles": roles, "lang": lang, "hints": True}
|
user_data = {"roles": roles, "lang": lang, "hints": True}
|
||||||
user_crafty_data = {
|
user_crafty_data = {
|
||||||
|
@ -26,7 +26,7 @@ class ApiServersServerStdinHandler(BaseApiHandler):
|
|||||||
# if the user doesn't have Commands permission, return an error
|
# if the user doesn't have Commands permission, return an error
|
||||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||||
|
|
||||||
svr = self.controller.get_server_obj_optional(server_id)
|
svr = self.controller.servers.get_server_obj_optional(server_id)
|
||||||
if svr is None:
|
if svr is None:
|
||||||
# It's in auth_data[0] but not as a Server object
|
# It's in auth_data[0] but not as a Server object
|
||||||
logger.critical(
|
logger.critical(
|
||||||
|
@ -105,6 +105,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
|||||||
permissions = data.get("permissions", None)
|
permissions = data.get("permissions", None)
|
||||||
roles = data.get("roles", None)
|
roles = data.get("roles", None)
|
||||||
hints = data.get("hints", True)
|
hints = data.get("hints", True)
|
||||||
|
theme = data.get("theme", "default")
|
||||||
|
|
||||||
if username.lower() in ["system", ""]:
|
if username.lower() in ["system", ""]:
|
||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
@ -155,6 +156,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
|||||||
email,
|
email,
|
||||||
enabled,
|
enabled,
|
||||||
new_superuser,
|
new_superuser,
|
||||||
|
theme,
|
||||||
)
|
)
|
||||||
self.controller.users.update_user(
|
self.controller.users.update_user(
|
||||||
user_id,
|
user_id,
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
import libgravatar
|
|
||||||
import requests
|
|
||||||
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__)
|
||||||
@ -21,29 +19,5 @@ class ApiUsersUserPfpHandler(BaseApiHandler):
|
|||||||
f'User {auth_data[4]["user_id"]} is fetching the pfp for user {user_id}'
|
f'User {auth_data[4]["user_id"]} is fetching the pfp for user {user_id}'
|
||||||
)
|
)
|
||||||
|
|
||||||
# http://en.gravatar.com/site/implement/images/#rating
|
self.finish_json(200, {"status": "ok", "data": user["pfp"]})
|
||||||
if self.helper.get_setting("allow_nsfw_profile_pictures"):
|
return
|
||||||
rating = "x"
|
|
||||||
else:
|
|
||||||
rating = "g"
|
|
||||||
|
|
||||||
# Get grvatar hash for profile pictures
|
|
||||||
if user["email"] != "default@example.com" or "":
|
|
||||||
gravatar = libgravatar.Gravatar(libgravatar.sanitize_email(user["email"]))
|
|
||||||
url = gravatar.get_image(
|
|
||||||
size=80,
|
|
||||||
default="404",
|
|
||||||
force_default=False,
|
|
||||||
rating=rating,
|
|
||||||
filetype_extension=False,
|
|
||||||
use_ssl=True,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
requests.head(url).raise_for_status()
|
|
||||||
except requests.HTTPError as e:
|
|
||||||
logger.debug("Gravatar profile picture not found", exc_info=e)
|
|
||||||
else:
|
|
||||||
self.finish_json(200, {"status": "ok", "data": url})
|
|
||||||
return
|
|
||||||
|
|
||||||
self.finish_json(200, {"status": "ok", "data": None})
|
|
||||||
|
@ -5,8 +5,6 @@ import time
|
|||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.escape
|
import tornado.escape
|
||||||
import bleach
|
import bleach
|
||||||
import libgravatar
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||||
from app.classes.shared.helpers import Helpers
|
from app.classes.shared.helpers import Helpers
|
||||||
@ -133,34 +131,6 @@ class ServerHandler(BaseHandler):
|
|||||||
"superuser": superuser,
|
"superuser": superuser,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.helper.get_setting("allow_nsfw_profile_pictures"):
|
|
||||||
rating = "x"
|
|
||||||
else:
|
|
||||||
rating = "g"
|
|
||||||
|
|
||||||
if exec_user["email"] != "default@example.com" or "":
|
|
||||||
gravatar = libgravatar.Gravatar(
|
|
||||||
libgravatar.sanitize_email(exec_user["email"])
|
|
||||||
)
|
|
||||||
url = gravatar.get_image(
|
|
||||||
size=80,
|
|
||||||
default="404",
|
|
||||||
force_default=False,
|
|
||||||
rating=rating,
|
|
||||||
filetype_extension=False,
|
|
||||||
use_ssl=True,
|
|
||||||
) # + "?d=404"
|
|
||||||
try:
|
|
||||||
if requests.head(url).status_code != 404:
|
|
||||||
profile_url = url
|
|
||||||
else:
|
|
||||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
|
||||||
except:
|
|
||||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
|
||||||
else:
|
|
||||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
|
||||||
|
|
||||||
page_data["user_image"] = profile_url
|
|
||||||
if superuser:
|
if superuser:
|
||||||
page_data["roles"] = list_roles
|
page_data["roles"] = list_roles
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import time
|
|||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.options
|
import tornado.options
|
||||||
import tornado.httpserver
|
import tornado.httpserver
|
||||||
|
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||||
|
|
||||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||||
from app.classes.shared.console import Console
|
from app.classes.shared.console import Console
|
||||||
@ -33,115 +34,295 @@ class UploadHandler(BaseHandler):
|
|||||||
def prepare(self):
|
def prepare(self):
|
||||||
# Class & Function Defination
|
# Class & Function Defination
|
||||||
api_key, _token_data, exec_user = self.current_user
|
api_key, _token_data, exec_user = self.current_user
|
||||||
server_id = self.get_argument("server_id", None)
|
self.upload_type = str(self.request.headers.get("X-Content-Upload-Type"))
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.superuser
|
|
||||||
user_id = exec_user["user_id"]
|
|
||||||
stream_size_value = self.helper.get_setting("stream_size_GB")
|
|
||||||
|
|
||||||
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
if self.upload_type == "server_import":
|
||||||
|
superuser = exec_user["superuser"]
|
||||||
|
if api_key is not None:
|
||||||
|
superuser = superuser and api_key.superuser
|
||||||
|
user_id = exec_user["user_id"]
|
||||||
|
stream_size_value = self.helper.get_setting("stream_size_GB")
|
||||||
|
|
||||||
self.content_len = int(self.request.headers.get("Content-Length"))
|
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
||||||
if self.content_len > max_streamed_size:
|
|
||||||
logger.error(
|
|
||||||
f"User with ID {user_id} attempted to upload a file that"
|
|
||||||
f" exceeded the max body size."
|
|
||||||
)
|
|
||||||
self.helper.websocket_helper.broadcast_user(
|
|
||||||
user_id,
|
|
||||||
"send_start_error",
|
|
||||||
{
|
|
||||||
"error": self.helper.translation.translate(
|
|
||||||
"error",
|
|
||||||
"fileTooLarge",
|
|
||||||
self.controller.users.get_user_lang_by_id(user_id),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return
|
|
||||||
self.do_upload = True
|
|
||||||
|
|
||||||
if superuser:
|
self.content_len = int(self.request.headers.get("Content-Length"))
|
||||||
exec_user_server_permissions = (
|
if self.content_len > max_streamed_size:
|
||||||
self.controller.server_perms.list_defined_permissions()
|
logger.error(
|
||||||
)
|
f"User with ID {user_id} attempted to upload a file that"
|
||||||
elif api_key is not None:
|
f" exceeded the max body size."
|
||||||
exec_user_server_permissions = (
|
|
||||||
self.controller.server_perms.get_api_key_permissions_list(
|
|
||||||
api_key, server_id
|
|
||||||
)
|
)
|
||||||
)
|
self.helper.websocket_helper.broadcast_user(
|
||||||
else:
|
user_id,
|
||||||
exec_user_server_permissions = (
|
"send_start_error",
|
||||||
self.controller.server_perms.get_user_id_permissions_list(
|
{
|
||||||
exec_user["user_id"], server_id
|
"error": self.helper.translation.translate(
|
||||||
|
"error",
|
||||||
|
"fileTooLarge",
|
||||||
|
self.controller.users.get_user_lang_by_id(user_id),
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
)
|
return
|
||||||
|
self.do_upload = True
|
||||||
|
|
||||||
server_id = self.request.headers.get("X-ServerId", None)
|
if superuser:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.server_perms.list_defined_permissions()
|
||||||
|
)
|
||||||
|
elif api_key is not None:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.crafty_perms.get_api_key_permissions_list(api_key)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.crafty_perms.get_crafty_permissions_list(
|
||||||
|
exec_user["user_id"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
logger.warning("User ID not found in upload handler call")
|
logger.warning("User ID not found in upload handler call")
|
||||||
Console.warning("User ID not found in upload handler call")
|
Console.warning("User ID not found in upload handler call")
|
||||||
self.do_upload = False
|
self.do_upload = False
|
||||||
|
|
||||||
if server_id is None:
|
if (
|
||||||
logger.warning("Server ID not found in upload handler call")
|
EnumPermissionsCrafty.SERVER_CREATION
|
||||||
Console.warning("Server ID not found in upload handler call")
|
not in exec_user_server_permissions
|
||||||
self.do_upload = False
|
and not exec_user["superuser"]
|
||||||
|
):
|
||||||
|
logger.warning(
|
||||||
|
f"User {user_id} tried to upload a server" " without permissions!"
|
||||||
|
)
|
||||||
|
Console.warning(
|
||||||
|
f"User {user_id} tried to upload a server" " without permissions!"
|
||||||
|
)
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
if EnumPermissionsServer.FILES not in exec_user_server_permissions:
|
path = os.path.join(self.controller.project_root, "imports")
|
||||||
logger.warning(
|
# Delete existing files
|
||||||
f"User {user_id} tried to upload a file to "
|
if len(os.listdir(path)) > 0:
|
||||||
f"{server_id} without permissions!"
|
for item in os.listdir():
|
||||||
)
|
try:
|
||||||
Console.warning(
|
os.remove(os.path.join(path, item))
|
||||||
f"User {user_id} tried to upload a file to "
|
except:
|
||||||
f"{server_id} without permissions!"
|
logger.debug("Could not delete file on user server upload")
|
||||||
)
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
path = self.request.headers.get("X-Path", None)
|
self.helper.ensure_dir_exists(path)
|
||||||
filename = self.request.headers.get("X-FileName", None)
|
filename = self.request.headers.get("X-FileName", None)
|
||||||
full_path = os.path.join(path, filename)
|
if not str(filename).endswith(".zip"):
|
||||||
|
self.helper.websocket_helper.broadcast("close_upload_box", "error")
|
||||||
|
self.finish("error")
|
||||||
|
full_path = os.path.join(path, filename)
|
||||||
|
|
||||||
if not Helpers.in_path(
|
if self.do_upload:
|
||||||
Helpers.get_os_understandable_path(
|
try:
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
self.f = open(full_path, "wb")
|
||||||
),
|
except Exception as e:
|
||||||
full_path,
|
logger.error(f"Upload failed with error: {e}")
|
||||||
):
|
self.do_upload = False
|
||||||
print(
|
# If max_body_size is not set, you cannot upload files > 100MB
|
||||||
user_id,
|
self.request.connection.set_max_body_size(max_streamed_size)
|
||||||
server_id,
|
|
||||||
|
elif self.upload_type == "background":
|
||||||
|
superuser = exec_user["superuser"]
|
||||||
|
if api_key is not None:
|
||||||
|
superuser = superuser and api_key.superuser
|
||||||
|
user_id = exec_user["user_id"]
|
||||||
|
stream_size_value = self.helper.get_setting("stream_size_GB")
|
||||||
|
|
||||||
|
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
||||||
|
|
||||||
|
self.content_len = int(self.request.headers.get("Content-Length"))
|
||||||
|
if self.content_len > max_streamed_size:
|
||||||
|
logger.error(
|
||||||
|
f"User with ID {user_id} attempted to upload a file that"
|
||||||
|
f" exceeded the max body size."
|
||||||
|
)
|
||||||
|
self.helper.websocket_helper.broadcast_user(
|
||||||
|
user_id,
|
||||||
|
"send_start_error",
|
||||||
|
{
|
||||||
|
"error": self.helper.translation.translate(
|
||||||
|
"error",
|
||||||
|
"fileTooLarge",
|
||||||
|
self.controller.users.get_user_lang_by_id(user_id),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
self.do_upload = True
|
||||||
|
|
||||||
|
if superuser:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.server_perms.list_defined_permissions()
|
||||||
|
)
|
||||||
|
elif api_key is not None:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.server_perms.get_api_key_permissions_list(
|
||||||
|
api_key, server_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
exec_user["user_id"], server_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
server_id = self.request.headers.get("X-ServerId", None)
|
||||||
|
if server_id is None:
|
||||||
|
logger.warning("Server ID not found in upload handler call")
|
||||||
|
Console.warning("Server ID not found in upload handler call")
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
|
if user_id is None:
|
||||||
|
logger.warning("User ID not found in upload handler call")
|
||||||
|
Console.warning("User ID not found in upload handler call")
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
|
if EnumPermissionsServer.FILES not in exec_user_server_permissions:
|
||||||
|
logger.warning(
|
||||||
|
f"User {user_id} tried to upload a file to "
|
||||||
|
f"{server_id} without permissions!"
|
||||||
|
)
|
||||||
|
Console.warning(
|
||||||
|
f"User {user_id} tried to upload a file to "
|
||||||
|
f"{server_id} without permissions!"
|
||||||
|
)
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
|
path = self.request.headers.get("X-Path", None)
|
||||||
|
filename = self.request.headers.get("X-FileName", None)
|
||||||
|
full_path = os.path.join(path, filename)
|
||||||
|
|
||||||
|
if not Helpers.in_path(
|
||||||
Helpers.get_os_understandable_path(
|
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,
|
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} "
|
||||||
f"but the path is not inside of the server!"
|
f"but the path is not inside of the server!"
|
||||||
)
|
)
|
||||||
Console.warning(
|
Console.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} "
|
||||||
f"but the path is not inside of the server!"
|
f"but the path is not inside of the server!"
|
||||||
)
|
)
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
if self.do_upload:
|
|
||||||
try:
|
|
||||||
self.f = open(full_path, "wb")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Upload failed with error: {e}")
|
|
||||||
self.do_upload = False
|
self.do_upload = False
|
||||||
# If max_body_size is not set, you cannot upload files > 100MB
|
|
||||||
self.request.connection.set_max_body_size(max_streamed_size)
|
if self.do_upload:
|
||||||
|
try:
|
||||||
|
self.f = open(full_path, "wb")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Upload failed with error: {e}")
|
||||||
|
self.do_upload = False
|
||||||
|
# If max_body_size is not set, you cannot upload files > 100MB
|
||||||
|
self.request.connection.set_max_body_size(max_streamed_size)
|
||||||
|
else:
|
||||||
|
server_id = self.get_argument("server_id", None)
|
||||||
|
superuser = exec_user["superuser"]
|
||||||
|
if api_key is not None:
|
||||||
|
superuser = superuser and api_key.superuser
|
||||||
|
user_id = exec_user["user_id"]
|
||||||
|
stream_size_value = self.helper.get_setting("stream_size_GB")
|
||||||
|
|
||||||
|
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
||||||
|
|
||||||
|
self.content_len = int(self.request.headers.get("Content-Length"))
|
||||||
|
if self.content_len > max_streamed_size:
|
||||||
|
logger.error(
|
||||||
|
f"User with ID {user_id} attempted to upload a file that"
|
||||||
|
f" exceeded the max body size."
|
||||||
|
)
|
||||||
|
self.helper.websocket_helper.broadcast_user(
|
||||||
|
user_id,
|
||||||
|
"send_start_error",
|
||||||
|
{
|
||||||
|
"error": self.helper.translation.translate(
|
||||||
|
"error",
|
||||||
|
"fileTooLarge",
|
||||||
|
self.controller.users.get_user_lang_by_id(user_id),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
self.do_upload = True
|
||||||
|
|
||||||
|
if superuser:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.server_perms.list_defined_permissions()
|
||||||
|
)
|
||||||
|
elif api_key is not None:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.server_perms.get_api_key_permissions_list(
|
||||||
|
api_key, server_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
exec_user_server_permissions = (
|
||||||
|
self.controller.server_perms.get_user_id_permissions_list(
|
||||||
|
exec_user["user_id"], server_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
server_id = self.request.headers.get("X-ServerId", None)
|
||||||
|
if server_id is None:
|
||||||
|
logger.warning("Server ID not found in upload handler call")
|
||||||
|
Console.warning("Server ID not found in upload handler call")
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
|
if user_id is None:
|
||||||
|
logger.warning("User ID not found in upload handler call")
|
||||||
|
Console.warning("User ID not found in upload handler call")
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
|
if EnumPermissionsServer.FILES not in exec_user_server_permissions:
|
||||||
|
logger.warning(
|
||||||
|
f"User {user_id} tried to upload a file to "
|
||||||
|
f"{server_id} without permissions!"
|
||||||
|
)
|
||||||
|
Console.warning(
|
||||||
|
f"User {user_id} tried to upload a file to "
|
||||||
|
f"{server_id} without permissions!"
|
||||||
|
)
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
|
path = self.request.headers.get("X-Path", None)
|
||||||
|
filename = self.request.headers.get("X-FileName", None)
|
||||||
|
full_path = os.path.join(path, filename)
|
||||||
|
|
||||||
|
if not Helpers.in_path(
|
||||||
|
Helpers.get_os_understandable_path(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
||||||
|
),
|
||||||
|
full_path,
|
||||||
|
):
|
||||||
|
logger.warning(
|
||||||
|
f"User {user_id} tried to upload a file to {server_id} "
|
||||||
|
f"but the path is not inside of the server!"
|
||||||
|
)
|
||||||
|
Console.warning(
|
||||||
|
f"User {user_id} tried to upload a file to {server_id} "
|
||||||
|
f"but the path is not inside of the server!"
|
||||||
|
)
|
||||||
|
self.do_upload = False
|
||||||
|
|
||||||
|
if self.do_upload:
|
||||||
|
try:
|
||||||
|
self.f = open(full_path, "wb")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Upload failed with error: {e}")
|
||||||
|
self.do_upload = False
|
||||||
|
# If max_body_size is not set, you cannot upload files > 100MB
|
||||||
|
self.request.connection.set_max_body_size(max_streamed_size)
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
logger.info("Upload completed")
|
logger.info("Upload completed")
|
||||||
files_left = int(self.request.headers.get("X-Files-Left", None))
|
if self.upload_type == "server_files":
|
||||||
|
files_left = int(self.request.headers.get("X-Files-Left", None))
|
||||||
|
else:
|
||||||
|
files_left = 0
|
||||||
|
|
||||||
if self.do_upload:
|
if self.do_upload:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
@ -85,13 +85,16 @@ class WebSocketHelper:
|
|||||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||||
|
|
||||||
def broadcast_with_fn(self, filter_fn, event_type: str, data):
|
def broadcast_with_fn(self, filter_fn, event_type: str, data):
|
||||||
clients = list(filter(filter_fn, self.clients))
|
# assign self.clients to a static variable here so hopefully
|
||||||
|
# the set size won't change
|
||||||
|
static_clients = self.clients
|
||||||
|
clients = list(filter(filter_fn, static_clients))
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Sending to {len(clients)} out of {len(self.clients)} "
|
f"Sending to {len(clients)} out of {len(self.clients)} "
|
||||||
f"clients: {json.dumps({'event': event_type, 'data': data})}"
|
f"clients: {json.dumps({'event': event_type, 'data': data})}"
|
||||||
)
|
)
|
||||||
|
|
||||||
for client in clients:
|
for client in clients[:]:
|
||||||
try:
|
try:
|
||||||
self.send_message(client, event_type, data)
|
self.send_message(client, event_type, data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"major": 4,
|
"major": 4,
|
||||||
"minor": 0,
|
"minor": 0,
|
||||||
"sub": 13,
|
"sub": 16,
|
||||||
"meta": "beta"
|
"meta": "beta"
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
.select-css option {
|
.select-css option {
|
||||||
background-color: #1C1E2F;
|
background-color: var(--deep-bg);
|
||||||
color: white
|
color: var(--base-text)
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-css option:checked {
|
.select-css option:checked {
|
||||||
background-color: #1C1E2F;
|
background-color: var(--deep-bg);
|
||||||
color: white
|
color: var(--base-text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -3,68 +3,122 @@
|
|||||||
* License - https://fontawesome.com/license (Commercial License)
|
* License - https://fontawesome.com/license (Commercial License)
|
||||||
*/
|
*/
|
||||||
svg:not(:root).svg-inline--fa {
|
svg:not(:root).svg-inline--fa {
|
||||||
overflow: visible; }
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa {
|
.svg-inline--fa {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
vertical-align: -.125em; }
|
vertical-align: -.125em;
|
||||||
.svg-inline--fa.fa-lg {
|
}
|
||||||
vertical-align: -.225em; }
|
|
||||||
.svg-inline--fa.fa-w-1 {
|
.svg-inline--fa.fa-lg {
|
||||||
width: 0.0625em; }
|
vertical-align: -.225em;
|
||||||
.svg-inline--fa.fa-w-2 {
|
}
|
||||||
width: 0.125em; }
|
|
||||||
.svg-inline--fa.fa-w-3 {
|
.svg-inline--fa.fa-w-1 {
|
||||||
width: 0.1875em; }
|
width: 0.0625em;
|
||||||
.svg-inline--fa.fa-w-4 {
|
}
|
||||||
width: 0.25em; }
|
|
||||||
.svg-inline--fa.fa-w-5 {
|
.svg-inline--fa.fa-w-2 {
|
||||||
width: 0.3125em; }
|
width: 0.125em;
|
||||||
.svg-inline--fa.fa-w-6 {
|
}
|
||||||
width: 0.375em; }
|
|
||||||
.svg-inline--fa.fa-w-7 {
|
.svg-inline--fa.fa-w-3 {
|
||||||
width: 0.4375em; }
|
width: 0.1875em;
|
||||||
.svg-inline--fa.fa-w-8 {
|
}
|
||||||
width: 0.5em; }
|
|
||||||
.svg-inline--fa.fa-w-9 {
|
.svg-inline--fa.fa-w-4 {
|
||||||
width: 0.5625em; }
|
width: 0.25em;
|
||||||
.svg-inline--fa.fa-w-10 {
|
}
|
||||||
width: 0.625em; }
|
|
||||||
.svg-inline--fa.fa-w-11 {
|
.svg-inline--fa.fa-w-5 {
|
||||||
width: 0.6875em; }
|
width: 0.3125em;
|
||||||
.svg-inline--fa.fa-w-12 {
|
}
|
||||||
width: 0.75em; }
|
|
||||||
.svg-inline--fa.fa-w-13 {
|
.svg-inline--fa.fa-w-6 {
|
||||||
width: 0.8125em; }
|
width: 0.375em;
|
||||||
.svg-inline--fa.fa-w-14 {
|
}
|
||||||
width: 0.875em; }
|
|
||||||
.svg-inline--fa.fa-w-15 {
|
.svg-inline--fa.fa-w-7 {
|
||||||
width: 0.9375em; }
|
width: 0.4375em;
|
||||||
.svg-inline--fa.fa-w-16 {
|
}
|
||||||
width: 1em; }
|
|
||||||
.svg-inline--fa.fa-w-17 {
|
.svg-inline--fa.fa-w-8 {
|
||||||
width: 1.0625em; }
|
width: 0.5em;
|
||||||
.svg-inline--fa.fa-w-18 {
|
}
|
||||||
width: 1.125em; }
|
|
||||||
.svg-inline--fa.fa-w-19 {
|
.svg-inline--fa.fa-w-9 {
|
||||||
width: 1.1875em; }
|
width: 0.5625em;
|
||||||
.svg-inline--fa.fa-w-20 {
|
}
|
||||||
width: 1.25em; }
|
|
||||||
.svg-inline--fa.fa-pull-left {
|
.svg-inline--fa.fa-w-10 {
|
||||||
margin-right: .3em;
|
width: 0.625em;
|
||||||
width: auto; }
|
}
|
||||||
.svg-inline--fa.fa-pull-right {
|
|
||||||
margin-left: .3em;
|
.svg-inline--fa.fa-w-11 {
|
||||||
width: auto; }
|
width: 0.6875em;
|
||||||
.svg-inline--fa.fa-border {
|
}
|
||||||
height: 1.5em; }
|
|
||||||
.svg-inline--fa.fa-li {
|
.svg-inline--fa.fa-w-12 {
|
||||||
width: 2em; }
|
width: 0.75em;
|
||||||
.svg-inline--fa.fa-fw {
|
}
|
||||||
width: 1.25em; }
|
|
||||||
|
.svg-inline--fa.fa-w-13 {
|
||||||
|
width: 0.8125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-w-14 {
|
||||||
|
width: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-w-15 {
|
||||||
|
width: 0.9375em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-w-16 {
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-w-17 {
|
||||||
|
width: 1.0625em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-w-18 {
|
||||||
|
width: 1.125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-w-19 {
|
||||||
|
width: 1.1875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-w-20 {
|
||||||
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-pull-left {
|
||||||
|
margin-right: .3em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-pull-right {
|
||||||
|
margin-left: .3em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-border {
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-li {
|
||||||
|
width: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-fw {
|
||||||
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers svg.svg-inline--fa {
|
.fa-layers svg.svg-inline--fa {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -72,7 +126,8 @@ svg:not(:root).svg-inline--fa {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0; }
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers {
|
.fa-layers {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -80,30 +135,36 @@ svg:not(:root).svg-inline--fa {
|
|||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: -.125em;
|
vertical-align: -.125em;
|
||||||
width: 1em; }
|
width: 1em;
|
||||||
.fa-layers svg.svg-inline--fa {
|
}
|
||||||
-webkit-transform-origin: center center;
|
|
||||||
transform-origin: center center; }
|
|
||||||
|
|
||||||
.fa-layers-text, .fa-layers-counter {
|
.fa-layers svg.svg-inline--fa {
|
||||||
|
-webkit-transform-origin: center center;
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-layers-text,
|
||||||
|
.fa-layers-counter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center; }
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers-text {
|
.fa-layers-text {
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
-webkit-transform: translate(-50%, -50%);
|
-webkit-transform: translate(-50%, -50%);
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
-webkit-transform-origin: center center;
|
-webkit-transform-origin: center center;
|
||||||
transform-origin: center center; }
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers-counter {
|
.fa-layers-counter {
|
||||||
background-color: #ff253a;
|
background-color: #ff253a;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: #fff;
|
var(--base-text);
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
max-width: 5em;
|
max-width: 5em;
|
||||||
@ -114,18 +175,20 @@ svg:not(:root).svg-inline--fa {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
top: 0;
|
top: 0;
|
||||||
-webkit-transform: scale(0.25);
|
-webkit-transform: scale(0.25);
|
||||||
transform: scale(0.25);
|
transform: scale(0.25);
|
||||||
-webkit-transform-origin: top right;
|
-webkit-transform-origin: top right;
|
||||||
transform-origin: top right; }
|
transform-origin: top right;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers-bottom-right {
|
.fa-layers-bottom-right {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: auto;
|
top: auto;
|
||||||
-webkit-transform: scale(0.25);
|
-webkit-transform: scale(0.25);
|
||||||
transform: scale(0.25);
|
transform: scale(0.25);
|
||||||
-webkit-transform-origin: bottom right;
|
-webkit-transform-origin: bottom right;
|
||||||
transform-origin: bottom right; }
|
transform-origin: bottom right;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers-bottom-left {
|
.fa-layers-bottom-left {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -133,164 +196,207 @@ svg:not(:root).svg-inline--fa {
|
|||||||
right: auto;
|
right: auto;
|
||||||
top: auto;
|
top: auto;
|
||||||
-webkit-transform: scale(0.25);
|
-webkit-transform: scale(0.25);
|
||||||
transform: scale(0.25);
|
transform: scale(0.25);
|
||||||
-webkit-transform-origin: bottom left;
|
-webkit-transform-origin: bottom left;
|
||||||
transform-origin: bottom left; }
|
transform-origin: bottom left;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers-top-right {
|
.fa-layers-top-right {
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
-webkit-transform: scale(0.25);
|
-webkit-transform: scale(0.25);
|
||||||
transform: scale(0.25);
|
transform: scale(0.25);
|
||||||
-webkit-transform-origin: top right;
|
-webkit-transform-origin: top right;
|
||||||
transform-origin: top right; }
|
transform-origin: top right;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-layers-top-left {
|
.fa-layers-top-left {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: auto;
|
right: auto;
|
||||||
top: 0;
|
top: 0;
|
||||||
-webkit-transform: scale(0.25);
|
-webkit-transform: scale(0.25);
|
||||||
transform: scale(0.25);
|
transform: scale(0.25);
|
||||||
-webkit-transform-origin: top left;
|
-webkit-transform-origin: top left;
|
||||||
transform-origin: top left; }
|
transform-origin: top left;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-lg {
|
.fa-lg {
|
||||||
font-size: 1.33333em;
|
font-size: 1.33333em;
|
||||||
line-height: 0.75em;
|
line-height: 0.75em;
|
||||||
vertical-align: -.0667em; }
|
vertical-align: -.0667em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-xs {
|
.fa-xs {
|
||||||
font-size: .75em; }
|
font-size: .75em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-sm {
|
.fa-sm {
|
||||||
font-size: .875em; }
|
font-size: .875em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-1x {
|
.fa-1x {
|
||||||
font-size: 1em; }
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-2x {
|
.fa-2x {
|
||||||
font-size: 2em; }
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-3x {
|
.fa-3x {
|
||||||
font-size: 3em; }
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-4x {
|
.fa-4x {
|
||||||
font-size: 4em; }
|
font-size: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-5x {
|
.fa-5x {
|
||||||
font-size: 5em; }
|
font-size: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-6x {
|
.fa-6x {
|
||||||
font-size: 6em; }
|
font-size: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-7x {
|
.fa-7x {
|
||||||
font-size: 7em; }
|
font-size: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-8x {
|
.fa-8x {
|
||||||
font-size: 8em; }
|
font-size: 8em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-9x {
|
.fa-9x {
|
||||||
font-size: 9em; }
|
font-size: 9em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-10x {
|
.fa-10x {
|
||||||
font-size: 10em; }
|
font-size: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-fw {
|
.fa-fw {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 1.25em; }
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-ul {
|
.fa-ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin-left: 2.5em;
|
margin-left: 2.5em;
|
||||||
padding-left: 0; }
|
padding-left: 0;
|
||||||
.fa-ul > li {
|
}
|
||||||
position: relative; }
|
|
||||||
|
.fa-ul>li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-li {
|
.fa-li {
|
||||||
left: -2em;
|
left: -2em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 2em;
|
width: 2em;
|
||||||
line-height: inherit; }
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-border {
|
.fa-border {
|
||||||
border: solid 0.08em #eee;
|
border: solid 0.08em #eee;
|
||||||
border-radius: .1em;
|
border-radius: .1em;
|
||||||
padding: .2em .25em .15em; }
|
padding: .2em .25em .15em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-pull-left {
|
.fa-pull-left {
|
||||||
float: left; }
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-pull-right {
|
.fa-pull-right {
|
||||||
float: right; }
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.fa.fa-pull-left,
|
.fa.fa-pull-left,
|
||||||
.fas.fa-pull-left,
|
.fas.fa-pull-left,
|
||||||
.far.fa-pull-left,
|
.far.fa-pull-left,
|
||||||
.fal.fa-pull-left,
|
.fal.fa-pull-left,
|
||||||
.fab.fa-pull-left {
|
.fab.fa-pull-left {
|
||||||
margin-right: .3em; }
|
margin-right: .3em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa.fa-pull-right,
|
.fa.fa-pull-right,
|
||||||
.fas.fa-pull-right,
|
.fas.fa-pull-right,
|
||||||
.far.fa-pull-right,
|
.far.fa-pull-right,
|
||||||
.fal.fa-pull-right,
|
.fal.fa-pull-right,
|
||||||
.fab.fa-pull-right {
|
.fab.fa-pull-right {
|
||||||
margin-left: .3em; }
|
margin-left: .3em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-spin {
|
.fa-spin {
|
||||||
-webkit-animation: fa-spin 2s infinite linear;
|
-webkit-animation: fa-spin 2s infinite linear;
|
||||||
animation: fa-spin 2s infinite linear; }
|
animation: fa-spin 2s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-pulse {
|
.fa-pulse {
|
||||||
-webkit-animation: fa-spin 1s infinite steps(8);
|
-webkit-animation: fa-spin 1s infinite steps(8);
|
||||||
animation: fa-spin 1s infinite steps(8); }
|
animation: fa-spin 1s infinite steps(8);
|
||||||
|
}
|
||||||
|
|
||||||
@-webkit-keyframes fa-spin {
|
@-webkit-keyframes fa-spin {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: rotate(0deg);
|
-webkit-transform: rotate(0deg);
|
||||||
transform: rotate(0deg); }
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
-webkit-transform: rotate(360deg);
|
-webkit-transform: rotate(360deg);
|
||||||
transform: rotate(360deg); } }
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fa-spin {
|
@keyframes fa-spin {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: rotate(0deg);
|
-webkit-transform: rotate(0deg);
|
||||||
transform: rotate(0deg); }
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
-webkit-transform: rotate(360deg);
|
-webkit-transform: rotate(360deg);
|
||||||
transform: rotate(360deg); } }
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.fa-rotate-90 {
|
.fa-rotate-90 {
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
|
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
|
||||||
-webkit-transform: rotate(90deg);
|
-webkit-transform: rotate(90deg);
|
||||||
transform: rotate(90deg); }
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
.fa-rotate-180 {
|
.fa-rotate-180 {
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
|
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
|
||||||
-webkit-transform: rotate(180deg);
|
-webkit-transform: rotate(180deg);
|
||||||
transform: rotate(180deg); }
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
.fa-rotate-270 {
|
.fa-rotate-270 {
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
|
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
|
||||||
-webkit-transform: rotate(270deg);
|
-webkit-transform: rotate(270deg);
|
||||||
transform: rotate(270deg); }
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
|
||||||
.fa-flip-horizontal {
|
.fa-flip-horizontal {
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
|
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
|
||||||
-webkit-transform: scale(-1, 1);
|
-webkit-transform: scale(-1, 1);
|
||||||
transform: scale(-1, 1); }
|
transform: scale(-1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.fa-flip-vertical {
|
.fa-flip-vertical {
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
|
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
|
||||||
-webkit-transform: scale(1, -1);
|
-webkit-transform: scale(1, -1);
|
||||||
transform: scale(1, -1); }
|
transform: scale(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
.fa-flip-both, .fa-flip-horizontal.fa-flip-vertical {
|
.fa-flip-both,
|
||||||
|
.fa-flip-horizontal.fa-flip-vertical {
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
|
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
|
||||||
-webkit-transform: scale(-1, -1);
|
-webkit-transform: scale(-1, -1);
|
||||||
transform: scale(-1, -1); }
|
transform: scale(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
:root .fa-rotate-90,
|
:root .fa-rotate-90,
|
||||||
:root .fa-rotate-180,
|
:root .fa-rotate-180,
|
||||||
@ -299,13 +405,15 @@ svg:not(:root).svg-inline--fa {
|
|||||||
:root .fa-flip-vertical,
|
:root .fa-flip-vertical,
|
||||||
:root .fa-flip-both {
|
:root .fa-flip-both {
|
||||||
-webkit-filter: none;
|
-webkit-filter: none;
|
||||||
filter: none; }
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-stack {
|
.fa-stack {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 2.5em; }
|
width: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-stack-1x,
|
.fa-stack-1x,
|
||||||
.fa-stack-2x {
|
.fa-stack-2x {
|
||||||
@ -314,18 +422,22 @@ svg:not(:root).svg-inline--fa {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0; }
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa.fa-stack-1x {
|
.svg-inline--fa.fa-stack-1x {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
width: 1.25em; }
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa.fa-stack-2x {
|
.svg-inline--fa.fa-stack-2x {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
width: 2.5em; }
|
width: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.fa-inverse {
|
.fa-inverse {
|
||||||
color: #fff; }
|
var(--base-text);
|
||||||
|
}
|
||||||
|
|
||||||
.sr-only {
|
.sr-only {
|
||||||
border: 0;
|
border: 0;
|
||||||
@ -335,37 +447,46 @@ svg:not(:root).svg-inline--fa {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px; }
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.sr-only-focusable:active, .sr-only-focusable:focus {
|
.sr-only-focusable:active,
|
||||||
|
.sr-only-focusable:focus {
|
||||||
clip: auto;
|
clip: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
position: static;
|
position: static;
|
||||||
width: auto; }
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa .fa-primary {
|
.svg-inline--fa .fa-primary {
|
||||||
fill: var(--fa-primary-color, currentColor);
|
fill: var(--fa-primary-color, currentColor);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
opacity: var(--fa-primary-opacity, 1); }
|
opacity: var(--fa-primary-opacity, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa .fa-secondary {
|
.svg-inline--fa .fa-secondary {
|
||||||
fill: var(--fa-secondary-color, currentColor);
|
fill: var(--fa-secondary-color, currentColor);
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
opacity: var(--fa-secondary-opacity, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa.fa-swap-opacity .fa-primary {
|
.svg-inline--fa.fa-swap-opacity .fa-primary {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
opacity: var(--fa-secondary-opacity, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa.fa-swap-opacity .fa-secondary {
|
.svg-inline--fa.fa-swap-opacity .fa-secondary {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
opacity: var(--fa-primary-opacity, 1); }
|
opacity: var(--fa-primary-opacity, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa mask .fa-primary,
|
.svg-inline--fa mask .fa-primary,
|
||||||
.svg-inline--fa mask .fa-secondary {
|
.svg-inline--fa mask .fa-secondary {
|
||||||
fill: black; }
|
fill: black;
|
||||||
|
}
|
||||||
|
|
||||||
.fad.fa-inverse {
|
.fad.fa-inverse {
|
||||||
color: #fff; }
|
var(--base-text);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ data['lang_page'] }}">
|
<html lang="{{ data['lang_page'] }}" class="{{data['user_data'].get('theme', 'default')}}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<!-- Required meta tags -->
|
<!-- Required meta tags -->
|
||||||
@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="dark-theme">
|
<body>
|
||||||
<div class="container-scroller">
|
<div class="container-scroller">
|
||||||
<!-- partial:partials/_navbar.html -->
|
<!-- partial:partials/_navbar.html -->
|
||||||
<nav class="navbar default-layout col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
|
<nav class="navbar default-layout col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
|
||||||
@ -127,7 +127,7 @@
|
|||||||
width: 180px;
|
width: 180px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
background: #282a40;
|
background: var(--card-banner-bg);
|
||||||
transition: right 0.75s, opacity 0.75s, top 0.75s;
|
transition: right 0.75s, opacity 0.75s, top 0.75s;
|
||||||
right: -6rem;
|
right: -6rem;
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
@ -525,10 +525,6 @@
|
|||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$(window).unload(function () {
|
|
||||||
jQuery.get("/public/logout")
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
|
|
||||||
<li class="nav-item dropdown user-dropdown">
|
<li class="nav-item dropdown user-dropdown">
|
||||||
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
|
||||||
<img class="img-xs rounded-circle profile-picture" src="{{ data['user_image'] }}" alt="Profile image"> </a>
|
<img class="img-xs rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image"> </a>
|
||||||
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
|
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
|
||||||
<div class="dropdown-header text-center">
|
<div class="dropdown-header text-center">
|
||||||
<img class="img-md rounded-circle profile-picture" src="{{ data['user_image'] }}" alt="Profile image">
|
<img class="img-md rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image">
|
||||||
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
|
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
|
||||||
<p class="font-weight-light text-muted mb-0">Roles: </p>
|
<p class="font-weight-light text-muted mb-0">Roles: </p>
|
||||||
{% for r in data['user_role'] %}
|
{% for r in data['user_role'] %}
|
||||||
@ -46,4 +46,12 @@
|
|||||||
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{ translate('notify', 'logout', data['lang']) }}</a>
|
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{ translate('notify', 'logout', data['lang']) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function pfpError(image) {
|
||||||
|
image.onerror = "";
|
||||||
|
image.src = "/static/assets/images/faces-clipart/pic-3.png";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
</script>
|
@ -39,7 +39,7 @@
|
|||||||
<div class="card-body pt-0">
|
<div class="card-body pt-0">
|
||||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=config" role="tab" aria-selected="true">
|
<a class="nav-link active" href="" role="tab" aria-selected="true">
|
||||||
<i class="fas fa-cogs"></i>{{ translate('rolesConfig', 'config', data['lang']) }}</a>
|
<i class="fas fa-cogs"></i>{{ translate('rolesConfig', 'config', data['lang']) }}</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li class="nav-item">
|
<!-- <li class="nav-item">
|
||||||
@ -152,13 +152,13 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
border-bottom: var(--table-border-width) solid #383e5d;
|
border-bottom: var(--table-border-width) solid var(--outline);
|
||||||
transition: border-bottom-color 500ms;
|
transition: border-bottom-color 500ms;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
table.rotate-table > tbody td {
|
table.rotate-table > tbody td {
|
||||||
border-right: var(--table-border-width) solid #383e5d;
|
border-right: var(--table-border-width) solid var(--outline);
|
||||||
/* make sure this is at least as wide as sqrt(2) * height of the tallest letter in your font or the headers will overlap each other*/
|
/* make sure this is at least as wide as sqrt(2) * height of the tallest letter in your font or the headers will overlap each other*/
|
||||||
min-width: 30px;
|
min-width: 30px;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
|
@ -121,6 +121,19 @@ data['lang']) }}{% end %}
|
|||||||
{% end %}
|
{% end %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="theme">{{ translate('userConfig', 'userTheme', data['lang'])
|
||||||
|
}}</label>
|
||||||
|
<select class="form-select form-control form-control-lg select-css" id="language"
|
||||||
|
name="theme" form="user_form">
|
||||||
|
<option value="{{data['user'].get('theme', 'default')}}">{{data['user'].get('theme', 'default')}}</option>
|
||||||
|
{% for theme in data['themes'] %}
|
||||||
|
{% if theme != data['user'].get('theme', 'default') %}
|
||||||
|
<option value="{{theme}}">{{theme}}</option>
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
{% if data['superuser'] %}
|
{% if data['superuser'] %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label" for="manager">{{ translate('userConfig', 'selectManager',
|
<label class="form-label" for="manager">{{ translate('userConfig', 'selectManager',
|
||||||
|
@ -42,7 +42,6 @@
|
|||||||
<div class="col-md-6 col-sm-12">
|
<div class="col-md-6 col-sm-12">
|
||||||
<style>
|
<style>
|
||||||
.playerItem {
|
.playerItem {
|
||||||
background: #1c1e2f;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
|
@ -711,6 +711,7 @@
|
|||||||
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
|
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
|
||||||
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
|
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
|
||||||
xmlHttpRequest.setRequestHeader('X-Path', path);
|
xmlHttpRequest.setRequestHeader('X-Path', path);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', 'server_files')
|
||||||
xmlHttpRequest.setRequestHeader('X-Files-Left', left);
|
xmlHttpRequest.setRequestHeader('X-Files-Left', left);
|
||||||
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
||||||
xmlHttpRequest.setRequestHeader('X-ServerId', serverId);
|
xmlHttpRequest.setRequestHeader('X-ServerId', serverId);
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div id="virt_console" class=""
|
<div id="virt_console" class=""
|
||||||
style="font-size: .8em; padding: 5px 10px; border: 1px solid #383e5d; background-color:#2a2c44;height:500px; overflow: scroll;">
|
style="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 />
|
||||||
|
@ -145,17 +145,35 @@
|
|||||||
data['lang']) }}</small> </label>
|
data['lang']) }}</small> </label>
|
||||||
<select id="parent" name="parent" class="form-control form-control-lg select-css"
|
<select id="parent" name="parent" class="form-control form-control-lg select-css"
|
||||||
value="{{ data['schedule']['action'] }}">
|
value="{{ data['schedule']['action'] }}">
|
||||||
{% for schedule in data['schedules'] %}
|
{% if data['parent'] %}
|
||||||
{% if schedule.schedule_id != data['schedule']['schedule_id'] %}
|
<option id="{{data['parent']['schedule_id']}}" value="{{data['parent']['schedule_id']}}">
|
||||||
{% if schedule.interval != '' %}
|
{{data['parent']['name']}} | {{data['parent']['command']}} | {{data['parent']['interval']}}
|
||||||
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
</option>
|
||||||
{{schedule.name}} | {{schedule.command}} | {{schedule.interval}} {{
|
{% for schedule in data['schedules'] %}
|
||||||
schedule.interval_type}}</option>
|
{% if schedule.schedule_id != data['schedule']['schedule_id'] and schedule.schedule_id != data['parent']['schedule_id'] %}
|
||||||
|
{% if schedule.interval != '' %}
|
||||||
|
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
||||||
|
{{schedule.name}} | {{schedule.command}} | {{schedule.interval}} {{
|
||||||
|
schedule.interval_type}}</option>
|
||||||
|
{% else %}
|
||||||
|
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
||||||
|
{{schedule.name}} | {{schedule.command}} | {{schedule.cron_string}}</option>
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
{% for schedule in data['schedules'] %}
|
||||||
{{schedule.name}} | {{schedule.command}} | {{schedule.cron_string}}</option>
|
{% if schedule.schedule_id != data['schedule']['schedule_id'] and schedule.schedule_id %}
|
||||||
{% end %}
|
{% if schedule.interval != '' %}
|
||||||
{% end %}
|
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
||||||
|
{{schedule.name}} | {{schedule.command}} | {{schedule.interval}} {{
|
||||||
|
schedule.interval_type}}</option>
|
||||||
|
{% else %}
|
||||||
|
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
||||||
|
{{schedule.name}} | {{schedule.command}} | {{schedule.cron_string}}</option>
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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>
|
||||||
@ -40,13 +41,21 @@
|
|||||||
</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">{{
|
||||||
|
translate('serverDetails', 'reset', data['lang']) }}</button>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div id="virt_console" class="" style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid #383e5d; background-color:#2a2c44;height:500px; overflow: scroll;"></div>
|
<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;">
|
||||||
|
</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" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" autofocus="">
|
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command"
|
||||||
|
name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}"
|
||||||
|
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>
|
||||||
@ -54,30 +63,60 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
||||||
{% if data['server_stats']['updating']%}
|
{% if data['server_stats']['updating']%}
|
||||||
<div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
<div id="update_control_buttons"
|
||||||
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled"><i
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'updating', data['lang']) }}</button>
|
style="visibility: visible">
|
||||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
<button onclick="" id="start-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']) }}</button>
|
class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
|
||||||
|
translate('serverTerm', 'updating', data['lang']) }}</button>
|
||||||
|
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
||||||
|
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
||||||
|
data['lang']) %}</button>
|
||||||
|
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
||||||
|
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
||||||
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
{% elif data['waiting_start'] %}
|
{% elif data['waiting_start'] %}
|
||||||
<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" style="visibility: visible">
|
<div id="control_buttons"
|
||||||
<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" title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', 'starting', data['lang']) }}</button>
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
style="visibility: visible">
|
||||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
<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"
|
||||||
|
title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm',
|
||||||
|
'starting', data['lang']) }}</button>
|
||||||
|
<button onclick="" id="restart-btn" style="max-width: 7rem;"
|
||||||
|
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
||||||
|
data['lang']) %}</button>
|
||||||
|
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
||||||
|
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
||||||
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
{% elif data['importing'] %}
|
{% elif data['importing'] %}
|
||||||
<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" style="visibility: visible">
|
<div id="control_buttons"
|
||||||
<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> {{ translate('serverTerm', 'importing',
|
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
|
style="visibility: visible">
|
||||||
|
<button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;"
|
||||||
|
class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
|
||||||
|
translate('serverTerm', 'importing',
|
||||||
data['lang']) }}</button>
|
data['lang']) }}</button>
|
||||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
<button onclick="" id="restart-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']) }}</button>
|
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
|
||||||
|
data['lang']) %}</button>
|
||||||
|
<button onclick="" id="stop-btn" style="max-width: 7rem;"
|
||||||
|
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
|
||||||
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<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" style="visibility: visible">
|
<div id="control_buttons"
|
||||||
<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="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||||
<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']) %}</button>
|
style="visibility: visible">
|
||||||
<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>
|
<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>
|
||||||
|
<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'])
|
||||||
|
%}</button>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -193,9 +232,12 @@
|
|||||||
function new_line_handler(data) {
|
function new_line_handler(data) {
|
||||||
$('#virt_console').append(data.line)
|
$('#virt_console').append(data.line)
|
||||||
const elem = document.getElementById('virt_console');
|
const elem = document.getElementById('virt_console');
|
||||||
const scrollDiff = (elem.scrollHeight - elem.scrollTop) - elem.clientHeight;
|
try {
|
||||||
if (!$("#stop_scroll").is(':checked') && scrollDiff < 450) {
|
if (!scrolled) {
|
||||||
scrollConsole()
|
scrollConsole();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
scrollConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +335,30 @@
|
|||||||
return nextCommand;
|
return nextCommand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chkScroll = (e) => {
|
||||||
|
const elem = $(e.currentTarget);
|
||||||
|
if (Math.round(elem[0].scrollHeight - elem.scrollTop()) <= elem.outerHeight()) {
|
||||||
|
document.getElementById("to-bottom").style.visibility = "hidden";
|
||||||
|
scrolled = false;
|
||||||
|
} else {
|
||||||
|
document.getElementById("to-bottom").style.visibility = "visible";
|
||||||
|
scrolled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToBottom = (id) => {
|
||||||
|
const element = $(`#virt_console`);
|
||||||
|
element.animate({
|
||||||
|
scrollTop: element.prop("scrollHeight")
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(() => {
|
||||||
|
var scrolled = false;
|
||||||
|
$('#virt_console').on('scroll', chkScroll);
|
||||||
|
$('#to-bottom').on('click', scrollToBottom)
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
@ -8,7 +8,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- View for Large screen -->
|
<!-- View for Large screen -->
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="content-wrapper col-md login-modal d-none d-sm-block" style="background-color: #222437;">
|
<div class="content-wrapper col-md login-modal d-none d-sm-block" style="background-color: var(--dropdown-bg);">
|
||||||
<img src="/static/assets/images/logo_long.png" style='width: 25%; margin-left: 38%;'>
|
<img src="/static/assets/images/logo_long.png" style='width: 25%; margin-left: 38%;'>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@ -40,7 +40,8 @@
|
|||||||
</td>
|
</td>
|
||||||
<td id="server_motd_{{ server['stats']['server_id']['server_id'] }}">
|
<td id="server_motd_{{ server['stats']['server_id']['server_id'] }}">
|
||||||
{% if server['stats']['desc'] != 'False' %}
|
{% if server['stats']['desc'] != 'False' %}
|
||||||
<img src="/static/assets/images/pack.png" alt="icon" style="-webkit-filter:grayscale(100%); filter:grayscale(100%)" />
|
<img src="/static/assets/images/pack.png" alt="icon"
|
||||||
|
style="-webkit-filter:grayscale(100%); filter:grayscale(100%)" />
|
||||||
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{
|
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{
|
||||||
server['stats']['desc'] }}</span> <br />
|
server['stats']['desc'] }}</span> <br />
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -94,21 +95,25 @@
|
|||||||
<h2 class="mb-0 container overflow-hidden">
|
<h2 class="mb-0 container overflow-hidden">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-8 mx-0 px-0">
|
<div class="col-8 mx-0 px-0">
|
||||||
<a id="m_server_name_{{ server['stats']['server_id']['server_id'] }}" class="btn btn-link d-flex justify-content-center" type="button" data-toggle="collapse"
|
<a id="m_server_name_{{ server['stats']['server_id']['server_id'] }}"
|
||||||
data-target="#collapse-{{server['server_data']['server_id']}}" aria-expanded="false"
|
class="btn btn-link d-flex justify-content-center" type="button" data-toggle="collapse"
|
||||||
aria-controls="collapse-{{server['server_data']['server_id']}}">
|
data-target="#collapse-{{server['server_data']['server_id']}}" aria-expanded="false"
|
||||||
|
aria-controls="collapse-{{server['server_data']['server_id']}}">
|
||||||
<i class="fas fa-server"></i>
|
<i class="fas fa-server"></i>
|
||||||
{{ server['server_data']['server_name'] }}
|
{{ server['server_data']['server_name'] }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4 mx-0 px-0">
|
<div class="col-4 mx-0 px-0">
|
||||||
<a id="m_server_online_status_{{ server['stats']['server_id']['server_id'] }}" class="btn btn-link d-flex justify-content-center" type="button">
|
<a id="m_server_online_status_{{ server['stats']['server_id']['server_id'] }}"
|
||||||
|
class="btn btn-link d-flex justify-content-center" type="button">
|
||||||
{% if server['stats']['running'] %}
|
{% if server['stats']['running'] %}
|
||||||
<div id="m_server_players_{{ server['stats']['server_id']['server_id'] }}">
|
<div id="m_server_players_{{ server['stats']['server_id']['server_id'] }}">
|
||||||
<span class="text-success"><i class="fas fa-signal"></i> {{ server['stats']['online'] }} / {{ server['stats']['max'] }}</span>
|
<span class="text-success"><i class="fas fa-signal"></i> {{ server['stats']['online'] }} / {{
|
||||||
|
server['stats']['max'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang']) }}</span>
|
<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
|
||||||
|
data['lang']) }}</span>
|
||||||
{% end %}
|
{% end %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -117,12 +122,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse"
|
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse"
|
||||||
aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
|
aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if server['stats']['int_ping_results'] != 'False' %}
|
{% if server['stats']['int_ping_results'] != 'False' %}
|
||||||
<div id="m_server_motd_{{ server['stats']['server_id']['server_id'] }}" class="media">
|
<div id="m_server_motd_{{ server['stats']['server_id']['server_id'] }}" class="media">
|
||||||
{% if server['stats']['desc'] != 'False' %}
|
{% if server['stats']['desc'] != 'False' %}
|
||||||
<img src="/static/assets/images/pack.png" class="w-25 mr-3" alt="icon" style="-webkit-filter:grayscale(100%); filter:grayscale(100%);" />
|
<img src="/static/assets/images/pack.png" class="w-25 mr-3" alt="icon"
|
||||||
|
style="-webkit-filter:grayscale(100%); filter:grayscale(100%);" />
|
||||||
{% end %}
|
{% end %}
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
{% if server['stats']['desc'] != 'False' %}
|
{% if server['stats']['desc'] != 'False' %}
|
||||||
@ -143,7 +149,8 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div id="m_server_motd_{{ server['stats']['server_id']['server_id'] }}">
|
<div id="m_server_motd_{{ server['stats']['server_id']['server_id'] }}">
|
||||||
<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Crafty can't get infos from this Server </span>
|
<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Crafty can't get infos from
|
||||||
|
this Server </span>
|
||||||
</div>
|
</div>
|
||||||
<div id="m_server_version_{{ server['stats']['server_id']['server_id'] }}"></div>
|
<div id="m_server_version_{{ server['stats']['server_id']['server_id'] }}"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -313,7 +313,8 @@
|
|||||||
<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="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
|
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
|
||||||
|
data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -321,9 +322,154 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 grid-margin">
|
<div class="col-sm-6 grid-margin stretch-card">
|
||||||
<img id="op_logo" style="filter: grayscale(10%); opacity: .1;" src="../../static/assets/images/logo_small.svg"
|
<div class="card">
|
||||||
alt="Crafty logo" />
|
<div class="card-body">
|
||||||
|
|
||||||
|
<h4>{{ translate('serverWizard', 'uploadZip', data['lang']) }}</h4>
|
||||||
|
<br />
|
||||||
|
<p class="card-description">
|
||||||
|
|
||||||
|
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
||||||
|
{% raw xsrf_form_html() %}
|
||||||
|
<input type="hidden" value="import_zip" name="create_type">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="server_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">Server Upload </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()">UPLOAD</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="lower_half" style="visibility: hidden;">
|
||||||
|
<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 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="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',
|
||||||
|
data['lang']) }}</small></h4>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<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="port4" name="port" value="19132" step="1" min="1"
|
||||||
|
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">
|
||||||
|
{{ 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">×</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-upload" 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>
|
||||||
|
<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>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -343,7 +489,7 @@
|
|||||||
z-index: 200;
|
z-index: 200;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #2a2c44;
|
background-color: var(--card-banner-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-option {
|
.menu-option {
|
||||||
@ -412,6 +558,65 @@
|
|||||||
|
|
||||||
{% block js%}
|
{% block js%}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
var file;
|
||||||
|
function sendFile() {
|
||||||
|
file = $("#file")[0].files[0]
|
||||||
|
document.getElementById("upload_input").innerHTML = '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i class="fa-solid fa-spinner"></i></div></div>'
|
||||||
|
let xmlHttpRequest = new XMLHttpRequest();
|
||||||
|
let token = getCookie("_xsrf")
|
||||||
|
let fileName = file.name
|
||||||
|
let target = '/upload'
|
||||||
|
let mimeType = file.type
|
||||||
|
let size = file.size
|
||||||
|
let type = 'server_import'
|
||||||
|
|
||||||
|
xmlHttpRequest.open('POST', target, true);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
||||||
|
xmlHttpRequest.addEventListener('load', (event) => {
|
||||||
|
if (event.target.responseText == 'success') {
|
||||||
|
console.log('Upload for file', file.name, 'was successful!')
|
||||||
|
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
|
||||||
|
document.getElementById("lower_half").style.visibility = "visible";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('Upload failed with response: ' + event.target.responseText);
|
||||||
|
doUpload = false;
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
xmlHttpRequest.addEventListener('error', (e) => {
|
||||||
|
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
||||||
|
}, false);
|
||||||
|
xmlHttpRequest.send(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("root_upload_button").addEventListener("click", function () {
|
||||||
|
if (file) {
|
||||||
|
if (document.getElementById('root_upload_button').classList.contains('clicked')) {
|
||||||
|
document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input-upload" 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>'
|
||||||
|
} else {
|
||||||
|
document.getElementById('root_upload_button').classList.add('clicked')
|
||||||
|
}
|
||||||
|
var token = getCookie("_xsrf");
|
||||||
|
var dialog = bootbox.dialog({
|
||||||
|
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||||
|
closeButton: false
|
||||||
|
});
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
headers: { 'X-XSRFToken': token },
|
||||||
|
url: '/ajax/unzip_server?id=-1&file=' + file.name,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bootbox.alert("You must input a path before selecting this button");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function eula_confirm() {
|
function eula_confirm() {
|
||||||
bootbox.confirm({
|
bootbox.confirm({
|
||||||
title: "{% raw translate('error', 'eulaTitle', data['lang']) %}",
|
title: "{% raw translate('error', 'eulaTitle', data['lang']) %}",
|
||||||
@ -438,6 +643,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
$(".tree-reset").on("click", function () {
|
||||||
|
location.href = "/server/bedrock_step1";
|
||||||
|
});
|
||||||
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 != "") {
|
||||||
if (document.getElementById('root_files_button').classList.contains('clicked')) {
|
if (document.getElementById('root_files_button').classList.contains('clicked')) {
|
||||||
@ -488,7 +696,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTreeView(path) {
|
function getTreeView(path) {
|
||||||
document.getElementById('zip_submit').disabled = false;
|
if (document.getElementById("lower_half").visibility == "hidden") {
|
||||||
|
document.getElementById('zip_submit').disabled = false;
|
||||||
|
} else {
|
||||||
|
document.getElementById('upload_submit').disabled = false;
|
||||||
|
}
|
||||||
path = path
|
path = path
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -582,8 +794,11 @@
|
|||||||
x.remove()
|
x.remove()
|
||||||
}
|
}
|
||||||
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
||||||
|
document.getElementById('main-tree-input-upload').setAttribute('value', data.path)
|
||||||
getTreeView(data.path);
|
getTreeView(data.path);
|
||||||
show_file_tree();
|
show_file_tree();
|
||||||
|
$("#root_files_button").attr("disabled", "disabled");
|
||||||
|
$("#root_upload_button").attr("disabled", "disabled");
|
||||||
|
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
|
@ -423,7 +423,8 @@
|
|||||||
<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="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
|
<button type="button" class="btn btn-danger mr-2 tree-reset">{{ translate('serverWizard', 'resetForm',
|
||||||
|
data['lang'])
|
||||||
}}</button>
|
}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -431,9 +432,172 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 grid-margin">
|
<div class="col-sm-6 grid-margin stretch-card">
|
||||||
<img id="op_logo" style="filter: grayscale(10%); opacity: .1;" src="../../static/assets/images/logo_small.svg"
|
<div class="card">
|
||||||
alt="Crafty logo" />
|
<div class="card-body">
|
||||||
|
|
||||||
|
<h4>{{ translate('serverWizard', 'uploadZip', data['lang']) }}</h4>
|
||||||
|
<br />
|
||||||
|
<p class="card-description">
|
||||||
|
|
||||||
|
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
||||||
|
{% raw xsrf_form_html() %}
|
||||||
|
<input type="hidden" value="import_zip" name="create_type">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="server_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">Server Upload </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()">UPLOAD</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="lower_half" style="visibility: hidden;">
|
||||||
|
<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 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="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',
|
||||||
|
data['lang']) }}</small></h4>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
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">
|
||||||
|
{{ 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">×</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-upload" 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>
|
||||||
|
<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>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -457,7 +621,7 @@
|
|||||||
z-index: 200;
|
z-index: 200;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #2a2c44;
|
background-color: var(--card-banner-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-option {
|
.menu-option {
|
||||||
@ -548,271 +712,449 @@
|
|||||||
} else {
|
} else {
|
||||||
bootbox.alert("You must input a path before selecting this button");
|
bootbox.alert("You must input a path before selecting this button");
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
.scroll {
|
||||||
|
max - height: 12em;
|
||||||
|
overflow - y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu - btn {
|
||||||
|
font - size: 0.9em;
|
||||||
|
padding: 2px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
padding - top: 10px;
|
||||||
|
z - index: 200;
|
||||||
|
margin - top: 4px;
|
||||||
|
position: absolute;
|
||||||
|
background - color: #2a2c44;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu - option {
|
||||||
|
padding: 6px 20px 6px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100 %;
|
||||||
|
height: 100 %;
|
||||||
|
z - index: 100;
|
||||||
|
}
|
||||||
|
</style >
|
||||||
|
<style>
|
||||||
|
/* Remove default bullets */
|
||||||
|
.tree-view,
|
||||||
|
.tree-nested {
|
||||||
|
list - style - type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the items */
|
||||||
|
.tree-item,
|
||||||
|
.files-tree-title {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
/* Prevent text selection */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the caret/arrow with a unicode, and style it */
|
||||||
|
.tree-caret .fa-folder {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-caret .fa-folder-open {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||||
|
.tree-caret-down .fa-folder {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-caret-down .fa-folder-open {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the nested list */
|
||||||
|
.tree-nested {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#op_logo {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script>
|
||||||
|
var file;
|
||||||
|
function sendFile() {
|
||||||
|
file = $("#file")[0].files[0]
|
||||||
|
document.getElementById("upload_input").innerHTML = '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i class="fa-solid fa-spinner"></i></div></div>'
|
||||||
|
let xmlHttpRequest = new XMLHttpRequest();
|
||||||
|
let token = getCookie("_xsrf")
|
||||||
|
let fileName = file.name
|
||||||
|
let target = '/upload'
|
||||||
|
let mimeType = file.type
|
||||||
|
let size = file.size
|
||||||
|
let type = 'server_import'
|
||||||
|
|
||||||
|
xmlHttpRequest.open('POST', target, true);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
|
||||||
|
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
|
||||||
|
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
||||||
|
xmlHttpRequest.addEventListener('load', (event) => {
|
||||||
|
if (event.target.responseText == 'success') {
|
||||||
|
console.log('Upload for file', file.name, 'was successful!')
|
||||||
|
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
|
||||||
|
document.getElementById("lower_half").style.visibility = "visible";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('Upload failed with response: ' + event.target.responseText);
|
||||||
|
doUpload = false;
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
xmlHttpRequest.addEventListener('error', (e) => {
|
||||||
|
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
||||||
|
}, false);
|
||||||
|
xmlHttpRequest.send(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("root_files_button").addEventListener("click", function () {
|
||||||
|
if (document.forms["zip"]["server_path"].value != "") {
|
||||||
|
if (document.getElementById('root_files_button').classList.contains('clicked')) {
|
||||||
|
document.getElementById('main-tree-div').innerHTML = '<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>'
|
||||||
|
} else {
|
||||||
|
document.getElementById('root_files_button').classList.add('clicked')
|
||||||
|
}
|
||||||
|
path = document.forms["zip"]["server_path"].value;
|
||||||
|
console.log(document.forms["zip"]["server_path"].value)
|
||||||
|
var token = getCookie("_xsrf");
|
||||||
|
var dialog = bootbox.dialog({
|
||||||
|
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||||
|
closeButton: false
|
||||||
|
});
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
headers: {'X-XSRFToken': token },
|
||||||
|
url: '/ajax/unzip_server?id=-1&path=' + path,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bootbox.alert("You must input a path before selecting this button");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("root_upload_button").addEventListener("click", function () {
|
||||||
|
if (file) {
|
||||||
|
if (document.getElementById('root_upload_button').classList.contains('clicked')) {
|
||||||
|
document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input-upload" 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>'
|
||||||
|
} else {
|
||||||
|
document.getElementById('root_upload_button').classList.add('clicked')
|
||||||
|
}
|
||||||
|
var token = getCookie("_xsrf");
|
||||||
|
var dialog = bootbox.dialog({
|
||||||
|
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||||
|
closeButton: false
|
||||||
|
});
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
headers: {'X-XSRFToken': token },
|
||||||
|
url: '/ajax/unzip_server?id=-1&file=' + file.name,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bootbox.alert("You must input a path before selecting this button");
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function dropDown(event) {
|
$(".tree-reset").on("click", function () {
|
||||||
event.target.parentElement.children[1].classList.remove("d-none");
|
location.href = "/server/step1";
|
||||||
document.getElementById("overlay").classList.remove("d-none");
|
});
|
||||||
}
|
function dropDown(event) {
|
||||||
|
event.target.parentElement.children[1].classList.remove("d-none");
|
||||||
|
document.getElementById("overlay").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
function hide(event) {
|
function hide(event) {
|
||||||
var items = document.getElementsByClassName('menu');
|
var items = document.getElementsByClassName('menu');
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
item.classList.add("d-none");
|
item.classList.add("d-none");
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById("overlay").classList.add("d-none");
|
document.getElementById("overlay").classList.add("d-none");
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
console.log('ready');
|
console.log('ready');
|
||||||
var forms = $('form.server-wizard');
|
var forms = $('form.server-wizard');
|
||||||
forms.each(function (i, formEl) {
|
forms.each(function (i, formEl) {
|
||||||
var form = $(formEl);
|
var form = $(formEl);
|
||||||
var min = form.find('[name=min_memory]');
|
var min = form.find('[name=min_memory]');
|
||||||
var max = form.find('[name=max_memory]');
|
var max = form.find('[name=max_memory]');
|
||||||
console.log(form, min, max)
|
console.log(form, min, max)
|
||||||
min.change(function () {
|
min.change(function () {
|
||||||
check_sizes(max, min, 'min');
|
check_sizes(max, min, 'min');
|
||||||
});
|
});
|
||||||
max.change(function () {
|
max.change(function () {
|
||||||
check_sizes(max, min, 'max');
|
check_sizes(max, min, 'max');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
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: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}',
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function show_file_tree() {
|
|
||||||
$("#dir_select").modal();
|
|
||||||
}
|
|
||||||
|
|
||||||
function check_sizes(a, b, changed) {
|
|
||||||
max_mem = parseFloat(a.val());
|
|
||||||
min_mem = parseFloat(b.val());
|
|
||||||
if (max_mem < min_mem && changed === 'min') {
|
|
||||||
a.val(min_mem)
|
|
||||||
}
|
}
|
||||||
if (max_mem < min_mem && changed === 'max') {
|
|
||||||
b.val(max_mem)
|
function show_file_tree() {
|
||||||
|
$("#dir_select").modal();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function getTreeView(path) {
|
function check_sizes(a, b, changed) {
|
||||||
document.getElementById('zip_submit').disabled = false;
|
max_mem = parseFloat(a.val());
|
||||||
path = path
|
min_mem = parseFloat(b.val());
|
||||||
|
if (max_mem < min_mem && changed === 'min') {
|
||||||
|
a.val(min_mem)
|
||||||
|
}
|
||||||
|
if (max_mem < min_mem && changed === 'max') {
|
||||||
|
b.val(max_mem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
function getTreeView(path) {
|
||||||
type: "GET",
|
//If this value is still hidden we know the user is executing a zip import and not an upload
|
||||||
|
if (document.getElementById("lower_half").visibility == "hidden") {
|
||||||
|
document.getElementById('zip_submit').disabled = false;
|
||||||
|
} else {
|
||||||
|
document.getElementById('upload_submit').disabled = false;
|
||||||
|
}
|
||||||
|
path = path
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
url: '/ajax/get_zip_tree?id=-1&path=' + path,
|
url: '/ajax/get_zip_tree?id=-1&path=' + path,
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
console.log("got response:");
|
console.log("got response:");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
dataArr = data.split('\n');
|
dataArr = data.split('\n');
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||||
text = dataArr.join('\n');
|
text = dataArr.join('\n');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
document.getElementById('main-tree-div').innerHTML += text;
|
document.getElementById('main-tree-div').innerHTML += text;
|
||||||
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||||
} catch {
|
|
||||||
document.getElementById('files-tree').innerHTML = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
|
||||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getToggleMain(event) {
|
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
document.getElementById("files-tree").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getDirView(event) {
|
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
|
||||||
|
|
||||||
if (document.getElementById(path).classList.contains('clicked')) {
|
|
||||||
|
|
||||||
var toggler = document.getElementById(path + "span");
|
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
|
||||||
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: '/ajax/get_zip_dir?id=-1&path=' + path,
|
|
||||||
dataType: 'text',
|
|
||||||
success: function (data) {
|
|
||||||
console.log("got response:");
|
|
||||||
|
|
||||||
dataArr = data.split('\n');
|
|
||||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
||||||
text = dataArr.join('\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
document.getElementById(path + "span").classList.add('tree-caret-down');
|
|
||||||
document.getElementById(path).innerHTML += text;
|
|
||||||
document.getElementById(path).classList.add("clicked");
|
|
||||||
} catch {
|
} catch {
|
||||||
console.log("Bad")
|
document.getElementById('files-tree').innerHTML = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
var toggler = document.getElementById(path);
|
|
||||||
|
|
||||||
if (toggler.classList.contains('files-tree-title')) {
|
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
||||||
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
||||||
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
||||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (webSocket) {
|
|
||||||
webSocket.on('send_temp_path', function (data) {
|
|
||||||
setTimeout(function () {
|
|
||||||
var x = document.querySelector('.bootbox');
|
|
||||||
if (x) {
|
|
||||||
x.remove()
|
|
||||||
}
|
|
||||||
var x = document.querySelector('.modal-backdrop');
|
|
||||||
if (x) {
|
|
||||||
x.remove()
|
|
||||||
}
|
|
||||||
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
|
||||||
getTreeView(data.path);
|
|
||||||
show_file_tree();
|
|
||||||
|
|
||||||
}, 5000);
|
function getToggleMain(event) {
|
||||||
});
|
path = event.target.parentElement.getAttribute('data-path');
|
||||||
}
|
document.getElementById("files-tree").classList.toggle("d-block");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret");
|
||||||
|
}
|
||||||
|
|
||||||
function refreshCache() {
|
|
||||||
var token = getCookie("_xsrf")
|
function getDirView(event) {
|
||||||
document.getElementById("refresh-cache").classList.add("fa-spin")
|
path = event.target.parentElement.getAttribute('data-path');
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
if (document.getElementById(path).classList.contains('clicked')) {
|
||||||
headers: { 'X-XSRFToken': token },
|
|
||||||
|
var toggler = document.getElementById(path + "span");
|
||||||
|
|
||||||
|
if (toggler.classList.contains('files-tree-title')) {
|
||||||
|
document.getElementById(path + "ul").classList.toggle("d-block");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: '/ajax/get_zip_dir?id=-1&path=' + path,
|
||||||
|
dataType: 'text',
|
||||||
|
success: function (data) {
|
||||||
|
console.log("got response:");
|
||||||
|
|
||||||
|
dataArr = data.split('\n');
|
||||||
|
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||||
|
text = dataArr.join('\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.getElementById(path + "span").classList.add('tree-caret-down');
|
||||||
|
document.getElementById(path).innerHTML += text;
|
||||||
|
document.getElementById(path).classList.add("clicked");
|
||||||
|
} catch {
|
||||||
|
console.log("Bad")
|
||||||
|
}
|
||||||
|
|
||||||
|
var toggler = document.getElementById(path);
|
||||||
|
|
||||||
|
if (toggler.classList.contains('files-tree-title')) {
|
||||||
|
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
||||||
|
document.getElementById(path + "ul").classList.toggle("d-block");
|
||||||
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (webSocket) {
|
||||||
|
webSocket.on('send_temp_path', function (data) {
|
||||||
|
setTimeout(function () {
|
||||||
|
var x = document.querySelector('.bootbox');
|
||||||
|
if (x) {
|
||||||
|
x.remove()
|
||||||
|
}
|
||||||
|
var x = document.querySelector('.modal-backdrop');
|
||||||
|
if (x) {
|
||||||
|
x.remove()
|
||||||
|
}
|
||||||
|
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
||||||
|
document.getElementById('main-tree-input-upload').setAttribute('value', data.path)
|
||||||
|
getTreeView(data.path);
|
||||||
|
show_file_tree();
|
||||||
|
|
||||||
|
$("#root_files_button").attr("disabled", "disabled");
|
||||||
|
$("#root_upload_button").attr("disabled", "disabled");
|
||||||
|
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCache() {
|
||||||
|
var token = getCookie("_xsrf")
|
||||||
|
document.getElementById("refresh-cache").classList.add("fa-spin")
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
headers: {'X-XSRFToken': token },
|
||||||
url: '/ajax/jar_cache',
|
url: '/ajax/jar_cache',
|
||||||
success: function () {
|
success: function () {
|
||||||
document.getElementById("refresh-cache").classList.remove("fa-sync");
|
document.getElementById("refresh-cache").classList.remove("fa-sync");
|
||||||
document.getElementById("refresh-cache").classList.remove("fa-spin");
|
document.getElementById("refresh-cache").classList.remove("fa-spin");
|
||||||
document.getElementById("refresh-cache").classList.add("fa-check");
|
document.getElementById("refresh-cache").classList.add("fa-check");
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
location.reload();
|
location.reload();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var text = '{% raw data["js_server_types"] %}';
|
var text = '{% raw data["js_server_types"] %}';
|
||||||
var serverTypesLists = JSON.parse(text);
|
var serverTypesLists = JSON.parse(text);
|
||||||
/* CountryChange() is called from the onchange event of a select element.
|
/* CountryChange() is called from the onchange event of a select element.
|
||||||
* param selectObj - the select object which fired the on change event.
|
* param selectObj - the select object which fired the on change event.
|
||||||
*/
|
*/
|
||||||
function serverTypeChange(selectObj) {
|
function serverTypeChange(selectObj) {
|
||||||
// get the index of the selected option
|
// get the index of the selected option
|
||||||
var idx = document.getElementById('server_type').selectedIndex;
|
var idx = document.getElementById('server_type').selectedIndex;
|
||||||
// get the value of the selected option
|
// get the value of the selected option
|
||||||
var cSelect = document.getElementById("server");
|
var cSelect = document.getElementById("server");
|
||||||
try {
|
try {
|
||||||
var which = document.getElementById('server_type').options[idx].value;
|
var which = document.getElementById('server_type').options[idx].value;
|
||||||
} catch {
|
} catch {
|
||||||
|
while (cSelect.options.length > 0) {
|
||||||
|
cSelect.remove(0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let server_type = which.split('|')[0];
|
||||||
|
let server = which.split('|')[1];
|
||||||
|
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
||||||
|
let cList = serverTypesLists[server_type];
|
||||||
|
// get the country select element via its known id
|
||||||
|
var cSelect = document.getElementById("server");
|
||||||
|
// remove the current options from the country select
|
||||||
|
var len = cSelect.options.length;
|
||||||
while (cSelect.options.length > 0) {
|
while (cSelect.options.length > 0) {
|
||||||
cSelect.remove(0);
|
cSelect.remove(0);
|
||||||
}
|
}
|
||||||
return;
|
var newOption;
|
||||||
}
|
// create new options ordered by ascending
|
||||||
let server_type = which.split('|')[0];
|
cList[server].forEach(type => {
|
||||||
let server = which.split('|')[1];
|
newOption = document.createElement("option");
|
||||||
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
|
||||||
let cList = serverTypesLists[server_type];
|
|
||||||
// get the country select element via its known id
|
|
||||||
var cSelect = document.getElementById("server");
|
|
||||||
// remove the current options from the country select
|
|
||||||
var len = cSelect.options.length;
|
|
||||||
while (cSelect.options.length > 0) {
|
|
||||||
cSelect.remove(0);
|
|
||||||
}
|
|
||||||
var newOption;
|
|
||||||
// create new options ordered by ascending
|
|
||||||
cList[server].forEach(type => {
|
|
||||||
newOption = document.createElement("option");
|
|
||||||
newOption.value = which + "|" + type; // assumes option string and value are the same
|
newOption.value = which + "|" + type; // assumes option string and value are the same
|
||||||
newOption.text = type;
|
newOption.text = type;
|
||||||
// add the new option
|
// add the new option
|
||||||
try {
|
try {
|
||||||
cSelect.add(newOption); // this will fail in DOM browsers but is needed for IE
|
cSelect.add(newOption); // this will fail in DOM browsers but is needed for IE
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
cSelect.appendChild(newOption);
|
cSelect.appendChild(newOption);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function serverJarChange(selectObj) {
|
function serverJarChange(selectObj) {
|
||||||
let type_select = document.getElementById('server_jar')
|
let type_select = document.getElementById('server_jar')
|
||||||
let tidx = type_select.selectedIndex;
|
let tidx = type_select.selectedIndex;
|
||||||
let val = type_select.options[tidx].value;
|
let val = type_select.options[tidx].value;
|
||||||
if (val == 'None') {
|
if (val == 'None') {
|
||||||
|
var jcSelect = document.getElementById("server_type");
|
||||||
|
while (jcSelect.options.length > 0) {
|
||||||
|
jcSelect.remove(0);
|
||||||
|
}
|
||||||
|
serverTypeChange(selectObj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// get the index of the selected option
|
||||||
|
var jidx = selectObj.selectedIndex;
|
||||||
|
// get the value of the selected option
|
||||||
|
var jwhich = selectObj.options[jidx].value;
|
||||||
|
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
||||||
|
jcList = Object.keys(serverTypesLists[jwhich]);
|
||||||
|
// get the country select element via its known id
|
||||||
var jcSelect = document.getElementById("server_type");
|
var jcSelect = document.getElementById("server_type");
|
||||||
|
// remove the current options from the country select
|
||||||
|
var jlen = jcSelect.options.length;
|
||||||
while (jcSelect.options.length > 0) {
|
while (jcSelect.options.length > 0) {
|
||||||
jcSelect.remove(0);
|
jcSelect.remove(0);
|
||||||
}
|
}
|
||||||
serverTypeChange(selectObj);
|
var jnewOption;
|
||||||
return;
|
// create new options ordered by ascending
|
||||||
}
|
jcList.forEach(type => {
|
||||||
// get the index of the selected option
|
jnewOption = document.createElement("option");
|
||||||
var jidx = selectObj.selectedIndex;
|
|
||||||
// get the value of the selected option
|
|
||||||
var jwhich = selectObj.options[jidx].value;
|
|
||||||
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
|
||||||
jcList = Object.keys(serverTypesLists[jwhich]);
|
|
||||||
// get the country select element via its known id
|
|
||||||
var jcSelect = document.getElementById("server_type");
|
|
||||||
// remove the current options from the country select
|
|
||||||
var jlen = jcSelect.options.length;
|
|
||||||
while (jcSelect.options.length > 0) {
|
|
||||||
jcSelect.remove(0);
|
|
||||||
}
|
|
||||||
var jnewOption;
|
|
||||||
// create new options ordered by ascending
|
|
||||||
jcList.forEach(type => {
|
|
||||||
jnewOption = document.createElement("option");
|
|
||||||
jnewOption.value = jwhich + "|" + type; // assumes option string and value are the same
|
jnewOption.value = jwhich + "|" + type; // assumes option string and value are the same
|
||||||
jnewOption.text = type;
|
jnewOption.text = type;
|
||||||
// add the new option
|
// add the new option
|
||||||
try {
|
try {
|
||||||
jcSelect.add(jnewOption); // this will fail in DOM browsers but is needed for IE
|
jcSelect.add(jnewOption); // this will fail in DOM browsers but is needed for IE
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
jcSelect.appendChild(jnewOption);
|
jcSelect.appendChild(jnewOption);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
serverTypeChange(selectObj);
|
serverTypeChange(selectObj);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% end %}
|
{% end %}
|
19
app/migrations/20220912_user_pfp.py
Normal file
19
app/migrations/20220912_user_pfp.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by database migrator
|
||||||
|
import peewee
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(migrator, database, **kwargs):
|
||||||
|
migrator.add_columns(
|
||||||
|
"users",
|
||||||
|
pfp=peewee.CharField(default="/static/assets/images/faces-clipart/pic-3.png"),
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
Write your migrations here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def rollback(migrator, database, **kwargs):
|
||||||
|
migrator.drop_columns("users", ["pfp"])
|
||||||
|
"""
|
||||||
|
Write your rollback migrations here.
|
||||||
|
"""
|
16
app/migrations/20220926_user_theme.py
Normal file
16
app/migrations/20220926_user_theme.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Generated by database migrator
|
||||||
|
import peewee
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(migrator, database, **kwargs):
|
||||||
|
migrator.add_columns("users", theme=peewee.CharField(default="default"))
|
||||||
|
"""
|
||||||
|
Write your migrations here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def rollback(migrator, database, **kwargs):
|
||||||
|
migrator.drop_columns("users", ["theme"])
|
||||||
|
"""
|
||||||
|
Write your rollback migrations here.
|
||||||
|
"""
|
@ -457,7 +457,7 @@
|
|||||||
"absoluteZipPath": "Absoluter Pfad zu dem Server",
|
"absoluteZipPath": "Absoluter Pfad zu dem Server",
|
||||||
"addRole": "Server zu existierender Rolle hinzufügen",
|
"addRole": "Server zu existierender Rolle hinzufügen",
|
||||||
"autoCreate": "Wenn keine ausgewählt werden, wird Crafty eine erstellen!",
|
"autoCreate": "Wenn keine ausgewählt werden, wird Crafty eine erstellen!",
|
||||||
"bePatient": "Bitte haben Sie etwas Geduld, da wir ' + (importing ? 'import' : 'download')",
|
"bePatient": "Bitte haben Sie etwas Geduld, da wir ' + (importing ? 'import' : 'download') + '",
|
||||||
"buildServer": "Server erstellen!",
|
"buildServer": "Server erstellen!",
|
||||||
"clickRoot": "Hier klicken, um das Stammverzeichnis auszuwählen",
|
"clickRoot": "Hier klicken, um das Stammverzeichnis auszuwählen",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
|
@ -359,7 +359,8 @@
|
|||||||
"schedule": "Schedule",
|
"schedule": "Schedule",
|
||||||
"serverDetails": "Server Details",
|
"serverDetails": "Server Details",
|
||||||
"terminal": "Terminal",
|
"terminal": "Terminal",
|
||||||
"metrics": "Metrics"
|
"metrics": "Metrics",
|
||||||
|
"reset": "Reset Scroll"
|
||||||
},
|
},
|
||||||
"serverFiles": {
|
"serverFiles": {
|
||||||
"clickUpload": "Click here to select your files",
|
"clickUpload": "Click here to select your files",
|
||||||
@ -501,6 +502,7 @@
|
|||||||
"importServer": "Import an Existing Server",
|
"importServer": "Import an Existing Server",
|
||||||
"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",
|
||||||
"maxMem": "Maximum Memory",
|
"maxMem": "Maximum Memory",
|
||||||
"minMem": "Minimum Memory",
|
"minMem": "Minimum Memory",
|
||||||
"myNewServer": "My New Server",
|
"myNewServer": "My New Server",
|
||||||
@ -564,6 +566,7 @@
|
|||||||
"roleName": "Role Name",
|
"roleName": "Role Name",
|
||||||
"super": "Super User",
|
"super": "Super User",
|
||||||
"userLang": "User Language",
|
"userLang": "User Language",
|
||||||
|
"userTheme": "UI Theme",
|
||||||
"userName": "User Name",
|
"userName": "User Name",
|
||||||
"userNameDesc": "What do you want to call this user?",
|
"userNameDesc": "What do you want to call this user?",
|
||||||
"userRoles": "User Roles",
|
"userRoles": "User Roles",
|
||||||
|
Loading…
Reference in New Issue
Block a user