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/
|
||||
|
||||
.idea/
|
||||
/imports/
|
||||
/servers/
|
||||
/backups/
|
||||
/temp/
|
||||
|
@ -51,20 +51,9 @@ pylint:
|
||||
- if: "$CODE_QUALITY_DISABLED"
|
||||
when: never
|
||||
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
|
||||
before_script:
|
||||
- mkdir -p public/badges public/lint
|
||||
- echo undefined > public/badges/$CI_JOB_NAME.score
|
||||
script:
|
||||
- pylint --exit-zero --output-format=text $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**") | tee /tmp/pylint.txt
|
||||
- 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)"
|
||||
- pylint --exit-zero --load-plugins=pylint_gitlab --output-format=gitlab-codeclimate:codeclimate.json $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**")
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
reports:
|
||||
codequality: codeclimate.json
|
||||
when: always
|
||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@ -1,5 +1,5 @@
|
||||
# Changelog
|
||||
## --- [4.0.13] - 2022/TBD
|
||||
## --- [4.0.16] - 2022/TBD
|
||||
### New features
|
||||
TBD
|
||||
### Bug fixes
|
||||
@ -10,6 +10,44 @@ TBD
|
||||
TBD
|
||||
<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
|
||||
### New features
|
||||
- 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)
|
||||
[![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)
|
||||
[![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
|
||||
|
||||
## What is Crafty Controller?
|
||||
|
@ -4,7 +4,6 @@ import time
|
||||
import json
|
||||
import pathlib
|
||||
import typing as t
|
||||
import datetime
|
||||
|
||||
from app.classes.controllers.roles_controller import RolesController
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
|
@ -147,14 +147,28 @@ class UsersController:
|
||||
return HelperServers.get_total_owned_servers(exec_user_id)
|
||||
|
||||
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:
|
||||
user_crafty_data = {}
|
||||
# check if general user data was updated
|
||||
if user_data is None:
|
||||
user_data = {}
|
||||
# get current user data
|
||||
base_data = HelperUsers.get_user(user_id)
|
||||
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()
|
||||
removed_roles = set()
|
||||
|
||||
# search for changes in user data
|
||||
for key in user_data:
|
||||
if key == "user_id":
|
||||
continue
|
||||
@ -174,8 +188,10 @@ class UsersController:
|
||||
up_data["hints"] = user_data["hints"]
|
||||
elif base_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()
|
||||
logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}")
|
||||
|
||||
for role in added_roles:
|
||||
HelperUsers.get_or_create(user_id=user_id, role_id=role)
|
||||
permissions_mask = user_crafty_data.get("permissions_mask", "000")
|
||||
@ -225,6 +241,7 @@ class UsersController:
|
||||
email="default@example.com",
|
||||
enabled: bool = True,
|
||||
superuser: bool = False,
|
||||
theme="default",
|
||||
):
|
||||
return self.users_helper.add_user(
|
||||
username,
|
||||
@ -233,6 +250,7 @@ class UsersController:
|
||||
email=email,
|
||||
enabled=enabled,
|
||||
superuser=superuser,
|
||||
theme=theme,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -42,6 +42,8 @@ class Users(BaseModel):
|
||||
preparing = BooleanField(default=False)
|
||||
hints = BooleanField(default=True)
|
||||
manager = IntegerField(default=None, null=True)
|
||||
pfp = CharField(default="/static/assets/images/faces-clipart/pic-3.png")
|
||||
theme = CharField(default="default")
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
@ -209,6 +211,7 @@ class HelperUsers:
|
||||
email: t.Optional[str] = None,
|
||||
enabled: bool = True,
|
||||
superuser: bool = False,
|
||||
theme: str = "default",
|
||||
) -> str:
|
||||
if password is not None:
|
||||
pw_enc = self.helper.encode_pass(password)
|
||||
@ -220,9 +223,11 @@ class HelperUsers:
|
||||
Users.password: pw_enc,
|
||||
Users.email: email,
|
||||
Users.enabled: enabled,
|
||||
Users.pfp: self.helper.get_gravatar_image(email),
|
||||
Users.superuser: superuser,
|
||||
Users.created: Helpers.get_time_as_string(),
|
||||
Users.manager: manager,
|
||||
Users.theme: theme,
|
||||
}
|
||||
).execute()
|
||||
return user_id
|
||||
|
@ -226,18 +226,24 @@ class FileHelpers:
|
||||
comment, "utf-8"
|
||||
) # comments over 65535 bytes will be truncated
|
||||
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:
|
||||
dirs.remove(l_dir)
|
||||
ziproot = path_to_zip
|
||||
# iterate through list of files
|
||||
for file in files:
|
||||
# check if file/dir is in exclusions list.
|
||||
# Only proceed if not exluded.
|
||||
if (
|
||||
str(os.path.join(root, file)).replace("\\", "/")
|
||||
not in ex_replace
|
||||
and file != "crafty.sqlite"
|
||||
):
|
||||
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":
|
||||
zip_file.write(
|
||||
os.path.join(root, file),
|
||||
@ -254,12 +260,20 @@ class FileHelpers:
|
||||
f"Error backing up: {os.path.join(root, file)}!"
|
||||
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))
|
||||
# calcualte percentage based off total size and current archive size
|
||||
percent = round((total_bytes / dir_bytes) * 100, 2)
|
||||
# package results
|
||||
results = {
|
||||
"percent": percent,
|
||||
"total_files": self.helper.human_readable_file_size(dir_bytes),
|
||||
}
|
||||
# send status results to page.
|
||||
self.helper.websocket_helper.broadcast_page_params(
|
||||
"/panel/server_detail",
|
||||
{"id": str(server_id)},
|
||||
|
@ -20,6 +20,7 @@ import itertools
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
from contextlib import redirect_stderr, suppress
|
||||
import libgravatar
|
||||
from packaging import version as pkg_version
|
||||
|
||||
from app.classes.shared.null_writer import NullWriter
|
||||
@ -399,6 +400,10 @@ class Helpers:
|
||||
)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_themes():
|
||||
return ["default", "dark", "light", "ronald"]
|
||||
|
||||
@staticmethod
|
||||
def get_local_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
@ -659,6 +664,33 @@ class Helpers:
|
||||
return True
|
||||
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
|
||||
def get_file_contents(path: str, lines=100):
|
||||
|
||||
|
@ -242,32 +242,36 @@ class ServerInstance:
|
||||
"Detected nebulous java in start command. "
|
||||
"Replacing with full java path."
|
||||
)
|
||||
# Checks for Oracle Java. Only Oracle Java's helper will cause a re-exec.
|
||||
if "/Oracle/Java/" in str(self.helper.wtol_path(shutil.which("java"))):
|
||||
logger.info(
|
||||
"Oracle Java detected. Changing start command to avoid re-exec."
|
||||
)
|
||||
which_java_raw = self.helper.which_java()
|
||||
try:
|
||||
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."
|
||||
oracle_path = shutil.which("java")
|
||||
if oracle_path:
|
||||
# Checks for Oracle Java. Only Oracle Java's helper will cause a re-exec
|
||||
if "/Oracle/Java/" in str(self.helper.wtol_path(oracle_path)):
|
||||
logger.info(
|
||||
"Oracle Java detected. Changing"
|
||||
" start command to avoid re-exec."
|
||||
)
|
||||
java_path = ""
|
||||
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
|
||||
which_java_raw = self.helper.which_java()
|
||||
try:
|
||||
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 = ""
|
||||
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"])
|
||||
|
||||
# let's do some quick checking to make sure things actually exists
|
||||
|
@ -678,6 +678,24 @@ class TasksManager:
|
||||
logger.info(
|
||||
"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):
|
||||
self.controller.servers.check_for_old_logs()
|
||||
|
@ -383,6 +383,8 @@ class AjaxHandler(BaseHandler):
|
||||
zip_name = bleach.clean(self.get_argument("zip_file", None))
|
||||
svr_obj = self.controller.servers.get_server_obj(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":
|
||||
backup_path = svr_obj.backup_path
|
||||
if Helpers.validate_traversal(backup_path, zip_name):
|
||||
@ -401,6 +403,27 @@ class AjaxHandler(BaseHandler):
|
||||
self.controller.rename_backup_dir(
|
||||
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:
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
except:
|
||||
@ -424,6 +447,26 @@ class AjaxHandler(BaseHandler):
|
||||
self.controller.rename_backup_dir(
|
||||
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:
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
except:
|
||||
@ -433,6 +476,12 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
elif page == "unzip_server":
|
||||
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):
|
||||
self.helper.unzip_server(path, exec_user["user_id"])
|
||||
else:
|
||||
|
@ -104,7 +104,10 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
strip: bool = True,
|
||||
) -> t.Optional[str]:
|
||||
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]:
|
||||
if not isinstance(strip, bool):
|
||||
|
@ -8,7 +8,6 @@ import logging
|
||||
import threading
|
||||
import shlex
|
||||
import bleach
|
||||
import libgravatar
|
||||
import requests
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
@ -331,37 +330,6 @@ class PanelHandler(BaseHandler):
|
||||
"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":
|
||||
template = "panel/denied.html"
|
||||
|
||||
@ -549,7 +517,7 @@ class PanelHandler(BaseHandler):
|
||||
"log_path": server_temp_obj["log_path"],
|
||||
"executable": server_temp_obj["executable"],
|
||||
"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"],
|
||||
"executable_update_url": server_temp_obj[
|
||||
"executable_update_url"
|
||||
@ -910,6 +878,7 @@ class PanelHandler(BaseHandler):
|
||||
page_data["user"]["roles"] = set()
|
||||
page_data["user"]["hints"] = True
|
||||
page_data["superuser"] = superuser
|
||||
page_data["themes"] = self.helper.get_themes()
|
||||
|
||||
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
|
||||
self.redirect(
|
||||
@ -1009,6 +978,7 @@ class PanelHandler(BaseHandler):
|
||||
# We'll just default to basic for new schedules
|
||||
page_data["schedule"]["difficulty"] = "basic"
|
||||
page_data["schedule"]["interval_type"] = "days"
|
||||
page_data["parent"] = None
|
||||
|
||||
if not EnumPermissionsServer.SCHEDULE in page_data["user_permissions"]:
|
||||
if not superuser:
|
||||
@ -1091,10 +1061,15 @@ class PanelHandler(BaseHandler):
|
||||
page_data["schedule"]["interval_type"] = schedule.interval_type
|
||||
if schedule.interval_type == "reaction":
|
||||
difficulty = "reaction"
|
||||
page_data["parent"] = self.controller.management.get_scheduled_task(
|
||||
schedule.parent
|
||||
)
|
||||
elif schedule.cron_string == "":
|
||||
difficulty = "basic"
|
||||
page_data["parent"] = None
|
||||
else:
|
||||
difficulty = "advanced"
|
||||
page_data["parent"] = None
|
||||
page_data["schedule"]["difficulty"] = difficulty
|
||||
|
||||
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["servers_all"] = self.controller.servers.get_all_defined_servers()
|
||||
page_data["superuser"] = superuser
|
||||
page_data["themes"] = self.helper.get_themes()
|
||||
if page_data["user"]["manager"] is not None:
|
||||
page_data["manager"] = self.controller.users.get_user_by_id(
|
||||
page_data["user"]["manager"]
|
||||
@ -1726,8 +1702,14 @@ class PanelHandler(BaseHandler):
|
||||
# only check for time if it's number of days
|
||||
if interval_type == "days":
|
||||
sch_time = bleach.clean(self.get_argument("time", None))
|
||||
if int(interval) > 30:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid argument."
|
||||
" Days must be 30 or fewer."
|
||||
)
|
||||
return
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument("command", None))
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
@ -1743,7 +1725,7 @@ class PanelHandler(BaseHandler):
|
||||
delay = bleach.clean(self.get_argument("delay", None))
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument("command", None))
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
@ -1763,7 +1745,7 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument("command", None))
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
@ -1888,8 +1870,14 @@ class PanelHandler(BaseHandler):
|
||||
# only check for time if it's number of days
|
||||
if interval_type == "days":
|
||||
sch_time = bleach.clean(self.get_argument("time", None))
|
||||
if int(interval) > 30:
|
||||
self.redirect(
|
||||
"/panel/error?error=Invalid argument."
|
||||
" Days must be 30 or fewer."
|
||||
)
|
||||
return
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument("command", None))
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
@ -1904,7 +1892,7 @@ class PanelHandler(BaseHandler):
|
||||
delay = bleach.clean(self.get_argument("delay", None))
|
||||
parent = bleach.clean(self.get_argument("parent", None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument("command", None))
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
@ -1924,7 +1912,7 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
action = bleach.clean(self.get_argument("action", None))
|
||||
if action == "command":
|
||||
command = bleach.clean(self.get_argument("command", None))
|
||||
command = self.get_argument("command", None)
|
||||
elif action == "start":
|
||||
command = "start_server"
|
||||
elif action == "stop":
|
||||
@ -2025,6 +2013,7 @@ class PanelHandler(BaseHandler):
|
||||
user_id = bleach.clean(self.get_argument("id", None))
|
||||
user = self.controller.users.get_user_by_id(user_id)
|
||||
username = bleach.clean(self.get_argument("username", None).lower())
|
||||
theme = bleach.clean(self.get_argument("theme", "default"))
|
||||
if (
|
||||
username != self.controller.users.get_user_by_id(user_id)["username"]
|
||||
and username in self.controller.users.get_all_usernames()
|
||||
@ -2091,6 +2080,7 @@ class PanelHandler(BaseHandler):
|
||||
"email": email,
|
||||
"lang": lang,
|
||||
"hints": hints,
|
||||
"theme": theme,
|
||||
}
|
||||
self.controller.users.update_user(user_id, user_data=user_data)
|
||||
|
||||
@ -2127,6 +2117,7 @@ class PanelHandler(BaseHandler):
|
||||
"lang": lang,
|
||||
"superuser": superuser,
|
||||
"hints": hints,
|
||||
"theme": theme,
|
||||
}
|
||||
user_crafty_data = {
|
||||
"permissions_mask": permissions_mask,
|
||||
@ -2238,6 +2229,7 @@ class PanelHandler(BaseHandler):
|
||||
password1 = bleach.clean(self.get_argument("password1", None))
|
||||
email = bleach.clean(self.get_argument("email", "default@example.com"))
|
||||
enabled = int(float(self.get_argument("enabled", "0")))
|
||||
theme = bleach.clean(self.get_argument("theme"), "default")
|
||||
hints = True
|
||||
lang = bleach.clean(
|
||||
self.get_argument("lang", self.helper.get_setting("language"))
|
||||
@ -2293,6 +2285,7 @@ class PanelHandler(BaseHandler):
|
||||
email=email,
|
||||
enabled=enabled,
|
||||
superuser=new_superuser,
|
||||
theme=theme,
|
||||
)
|
||||
user_data = {"roles": roles, "lang": lang, "hints": True}
|
||||
user_crafty_data = {
|
||||
|
@ -26,7 +26,7 @@ class ApiServersServerStdinHandler(BaseApiHandler):
|
||||
# if the user doesn't have Commands permission, return an error
|
||||
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:
|
||||
# It's in auth_data[0] but not as a Server object
|
||||
logger.critical(
|
||||
|
@ -105,6 +105,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
||||
permissions = data.get("permissions", None)
|
||||
roles = data.get("roles", None)
|
||||
hints = data.get("hints", True)
|
||||
theme = data.get("theme", "default")
|
||||
|
||||
if username.lower() in ["system", ""]:
|
||||
return self.finish_json(
|
||||
@ -155,6 +156,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
||||
email,
|
||||
enabled,
|
||||
new_superuser,
|
||||
theme,
|
||||
)
|
||||
self.controller.users.update_user(
|
||||
user_id,
|
||||
|
@ -1,6 +1,4 @@
|
||||
import logging
|
||||
import libgravatar
|
||||
import requests
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
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}'
|
||||
)
|
||||
|
||||
# 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 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})
|
||||
self.finish_json(200, {"status": "ok", "data": user["pfp"]})
|
||||
return
|
||||
|
@ -5,8 +5,6 @@ import time
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import bleach
|
||||
import libgravatar
|
||||
import requests
|
||||
|
||||
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||
from app.classes.shared.helpers import Helpers
|
||||
@ -133,34 +131,6 @@ class ServerHandler(BaseHandler):
|
||||
"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:
|
||||
page_data["roles"] = list_roles
|
||||
|
||||
|
@ -4,6 +4,7 @@ import time
|
||||
import tornado.web
|
||||
import tornado.options
|
||||
import tornado.httpserver
|
||||
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
from app.classes.shared.console import Console
|
||||
@ -33,115 +34,295 @@ class UploadHandler(BaseHandler):
|
||||
def prepare(self):
|
||||
# Class & Function Defination
|
||||
api_key, _token_data, exec_user = self.current_user
|
||||
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")
|
||||
self.upload_type = str(self.request.headers.get("X-Content-Upload-Type"))
|
||||
|
||||
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"))
|
||||
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
|
||||
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
||||
|
||||
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
|
||||
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."
|
||||
)
|
||||
)
|
||||
else:
|
||||
exec_user_server_permissions = (
|
||||
self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id
|
||||
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
|
||||
|
||||
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:
|
||||
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 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 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 (
|
||||
EnumPermissionsCrafty.SERVER_CREATION
|
||||
not in exec_user_server_permissions
|
||||
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:
|
||||
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 = os.path.join(self.controller.project_root, "imports")
|
||||
# Delete existing files
|
||||
if len(os.listdir(path)) > 0:
|
||||
for item in os.listdir():
|
||||
try:
|
||||
os.remove(os.path.join(path, item))
|
||||
except:
|
||||
logger.debug("Could not delete file on user server upload")
|
||||
|
||||
path = self.request.headers.get("X-Path", None)
|
||||
filename = self.request.headers.get("X-FileName", None)
|
||||
full_path = os.path.join(path, filename)
|
||||
self.helper.ensure_dir_exists(path)
|
||||
filename = self.request.headers.get("X-FileName", None)
|
||||
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(
|
||||
Helpers.get_os_understandable_path(
|
||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
||||
),
|
||||
full_path,
|
||||
):
|
||||
print(
|
||||
user_id,
|
||||
server_id,
|
||||
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)
|
||||
|
||||
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(
|
||||
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}")
|
||||
):
|
||||
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 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):
|
||||
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:
|
||||
time.sleep(5)
|
||||
|
@ -85,13 +85,16 @@ class WebSocketHelper:
|
||||
self.broadcast_with_fn(filter_fn, event_type, 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(
|
||||
f"Sending to {len(clients)} out of {len(self.clients)} "
|
||||
f"clients: {json.dumps({'event': event_type, 'data': data})}"
|
||||
)
|
||||
|
||||
for client in clients:
|
||||
for client in clients[:]:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except Exception as e:
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": 13,
|
||||
"sub": 16,
|
||||
"meta": "beta"
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
.select-css option {
|
||||
background-color: #1C1E2F;
|
||||
color: white
|
||||
background-color: var(--deep-bg);
|
||||
color: var(--base-text)
|
||||
}
|
||||
|
||||
.select-css option:checked {
|
||||
background-color: #1C1E2F;
|
||||
color: white
|
||||
background-color: var(--deep-bg);
|
||||
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)
|
||||
*/
|
||||
svg:not(:root).svg-inline--fa {
|
||||
overflow: visible; }
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.svg-inline--fa {
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
height: 1em;
|
||||
overflow: visible;
|
||||
vertical-align: -.125em; }
|
||||
.svg-inline--fa.fa-lg {
|
||||
vertical-align: -.225em; }
|
||||
.svg-inline--fa.fa-w-1 {
|
||||
width: 0.0625em; }
|
||||
.svg-inline--fa.fa-w-2 {
|
||||
width: 0.125em; }
|
||||
.svg-inline--fa.fa-w-3 {
|
||||
width: 0.1875em; }
|
||||
.svg-inline--fa.fa-w-4 {
|
||||
width: 0.25em; }
|
||||
.svg-inline--fa.fa-w-5 {
|
||||
width: 0.3125em; }
|
||||
.svg-inline--fa.fa-w-6 {
|
||||
width: 0.375em; }
|
||||
.svg-inline--fa.fa-w-7 {
|
||||
width: 0.4375em; }
|
||||
.svg-inline--fa.fa-w-8 {
|
||||
width: 0.5em; }
|
||||
.svg-inline--fa.fa-w-9 {
|
||||
width: 0.5625em; }
|
||||
.svg-inline--fa.fa-w-10 {
|
||||
width: 0.625em; }
|
||||
.svg-inline--fa.fa-w-11 {
|
||||
width: 0.6875em; }
|
||||
.svg-inline--fa.fa-w-12 {
|
||||
width: 0.75em; }
|
||||
.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; }
|
||||
vertical-align: -.125em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-lg {
|
||||
vertical-align: -.225em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-1 {
|
||||
width: 0.0625em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-2 {
|
||||
width: 0.125em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-3 {
|
||||
width: 0.1875em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-4 {
|
||||
width: 0.25em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-5 {
|
||||
width: 0.3125em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-6 {
|
||||
width: 0.375em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-7 {
|
||||
width: 0.4375em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-8 {
|
||||
width: 0.5em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-9 {
|
||||
width: 0.5625em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-10 {
|
||||
width: 0.625em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-11 {
|
||||
width: 0.6875em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-w-12 {
|
||||
width: 0.75em;
|
||||
}
|
||||
|
||||
.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 {
|
||||
bottom: 0;
|
||||
@ -72,7 +126,8 @@ svg:not(:root).svg-inline--fa {
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0; }
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.fa-layers {
|
||||
display: inline-block;
|
||||
@ -80,30 +135,36 @@ svg:not(:root).svg-inline--fa {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
vertical-align: -.125em;
|
||||
width: 1em; }
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.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;
|
||||
position: absolute;
|
||||
text-align: center; }
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fa-layers-text {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.fa-layers-counter {
|
||||
background-color: #ff253a;
|
||||
border-radius: 1em;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
box-sizing: border-box;
|
||||
var(--base-text);
|
||||
height: 1.5em;
|
||||
line-height: 1;
|
||||
max-width: 5em;
|
||||
@ -114,18 +175,20 @@ svg:not(:root).svg-inline--fa {
|
||||
text-overflow: ellipsis;
|
||||
top: 0;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
.fa-layers-bottom-right {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
top: auto;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: bottom right;
|
||||
transform-origin: bottom right; }
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.fa-layers-bottom-left {
|
||||
bottom: 0;
|
||||
@ -133,164 +196,207 @@ svg:not(:root).svg-inline--fa {
|
||||
right: auto;
|
||||
top: auto;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: bottom left;
|
||||
transform-origin: bottom left; }
|
||||
transform-origin: bottom left;
|
||||
}
|
||||
|
||||
.fa-layers-top-right {
|
||||
right: 0;
|
||||
top: 0;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
.fa-layers-top-left {
|
||||
left: 0;
|
||||
right: auto;
|
||||
top: 0;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left; }
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.fa-lg {
|
||||
font-size: 1.33333em;
|
||||
line-height: 0.75em;
|
||||
vertical-align: -.0667em; }
|
||||
vertical-align: -.0667em;
|
||||
}
|
||||
|
||||
.fa-xs {
|
||||
font-size: .75em; }
|
||||
font-size: .75em;
|
||||
}
|
||||
|
||||
.fa-sm {
|
||||
font-size: .875em; }
|
||||
font-size: .875em;
|
||||
}
|
||||
|
||||
.fa-1x {
|
||||
font-size: 1em; }
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.fa-2x {
|
||||
font-size: 2em; }
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.fa-3x {
|
||||
font-size: 3em; }
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
.fa-4x {
|
||||
font-size: 4em; }
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
.fa-5x {
|
||||
font-size: 5em; }
|
||||
font-size: 5em;
|
||||
}
|
||||
|
||||
.fa-6x {
|
||||
font-size: 6em; }
|
||||
font-size: 6em;
|
||||
}
|
||||
|
||||
.fa-7x {
|
||||
font-size: 7em; }
|
||||
font-size: 7em;
|
||||
}
|
||||
|
||||
.fa-8x {
|
||||
font-size: 8em; }
|
||||
font-size: 8em;
|
||||
}
|
||||
|
||||
.fa-9x {
|
||||
font-size: 9em; }
|
||||
font-size: 9em;
|
||||
}
|
||||
|
||||
.fa-10x {
|
||||
font-size: 10em; }
|
||||
font-size: 10em;
|
||||
}
|
||||
|
||||
.fa-fw {
|
||||
text-align: center;
|
||||
width: 1.25em; }
|
||||
width: 1.25em;
|
||||
}
|
||||
|
||||
.fa-ul {
|
||||
list-style-type: none;
|
||||
margin-left: 2.5em;
|
||||
padding-left: 0; }
|
||||
.fa-ul > li {
|
||||
position: relative; }
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.fa-ul>li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fa-li {
|
||||
left: -2em;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
line-height: inherit; }
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.fa-border {
|
||||
border: solid 0.08em #eee;
|
||||
border-radius: .1em;
|
||||
padding: .2em .25em .15em; }
|
||||
padding: .2em .25em .15em;
|
||||
}
|
||||
|
||||
.fa-pull-left {
|
||||
float: left; }
|
||||
float: left;
|
||||
}
|
||||
|
||||
.fa-pull-right {
|
||||
float: right; }
|
||||
float: right;
|
||||
}
|
||||
|
||||
.fa.fa-pull-left,
|
||||
.fas.fa-pull-left,
|
||||
.far.fa-pull-left,
|
||||
.fal.fa-pull-left,
|
||||
.fab.fa-pull-left {
|
||||
margin-right: .3em; }
|
||||
margin-right: .3em;
|
||||
}
|
||||
|
||||
.fa.fa-pull-right,
|
||||
.fas.fa-pull-right,
|
||||
.far.fa-pull-right,
|
||||
.fal.fa-pull-right,
|
||||
.fab.fa-pull-right {
|
||||
margin-left: .3em; }
|
||||
margin-left: .3em;
|
||||
}
|
||||
|
||||
.fa-spin {
|
||||
-webkit-animation: fa-spin 2s infinite linear;
|
||||
animation: fa-spin 2s infinite linear; }
|
||||
animation: fa-spin 2s infinite linear;
|
||||
}
|
||||
|
||||
.fa-pulse {
|
||||
-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 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.fa-rotate-90 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg); }
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.fa-rotate-180 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg); }
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.fa-rotate-270 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
|
||||
-webkit-transform: rotate(270deg);
|
||||
transform: rotate(270deg); }
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.fa-flip-horizontal {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
|
||||
-webkit-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1); }
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
.fa-flip-vertical {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=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)";
|
||||
-webkit-transform: scale(-1, -1);
|
||||
transform: scale(-1, -1); }
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
|
||||
:root .fa-rotate-90,
|
||||
:root .fa-rotate-180,
|
||||
@ -299,13 +405,15 @@ svg:not(:root).svg-inline--fa {
|
||||
:root .fa-flip-vertical,
|
||||
:root .fa-flip-both {
|
||||
-webkit-filter: none;
|
||||
filter: none; }
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.fa-stack {
|
||||
display: inline-block;
|
||||
height: 2em;
|
||||
position: relative;
|
||||
width: 2.5em; }
|
||||
width: 2.5em;
|
||||
}
|
||||
|
||||
.fa-stack-1x,
|
||||
.fa-stack-2x {
|
||||
@ -314,18 +422,22 @@ svg:not(:root).svg-inline--fa {
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0; }
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-stack-1x {
|
||||
height: 1em;
|
||||
width: 1.25em; }
|
||||
width: 1.25em;
|
||||
}
|
||||
|
||||
.svg-inline--fa.fa-stack-2x {
|
||||
height: 2em;
|
||||
width: 2.5em; }
|
||||
width: 2.5em;
|
||||
}
|
||||
|
||||
.fa-inverse {
|
||||
color: #fff; }
|
||||
var(--base-text);
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0;
|
||||
@ -335,37 +447,46 @@ svg:not(:root).svg-inline--fa {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px; }
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.sr-only-focusable:active, .sr-only-focusable:focus {
|
||||
.sr-only-focusable:active,
|
||||
.sr-only-focusable:focus {
|
||||
clip: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
position: static;
|
||||
width: auto; }
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.svg-inline--fa .fa-primary {
|
||||
fill: var(--fa-primary-color, currentColor);
|
||||
opacity: 1;
|
||||
opacity: var(--fa-primary-opacity, 1); }
|
||||
opacity: var(--fa-primary-opacity, 1);
|
||||
}
|
||||
|
||||
.svg-inline--fa .fa-secondary {
|
||||
fill: var(--fa-secondary-color, currentColor);
|
||||
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 {
|
||||
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 {
|
||||
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-secondary {
|
||||
fill: black; }
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.fad.fa-inverse {
|
||||
color: #fff; }
|
||||
var(--base-text);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ data['lang_page'] }}">
|
||||
<html lang="{{ data['lang_page'] }}" class="{{data['user_data'].get('theme', 'default')}}">
|
||||
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
@ -54,7 +54,7 @@
|
||||
|
||||
</head>
|
||||
|
||||
<body class="dark-theme">
|
||||
<body>
|
||||
<div class="container-scroller">
|
||||
<!-- partial:partials/_navbar.html -->
|
||||
<nav class="navbar default-layout col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
|
||||
@ -127,7 +127,7 @@
|
||||
width: 180px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
background: #282a40;
|
||||
background: var(--card-banner-bg);
|
||||
transition: right 0.75s, opacity 0.75s, top 0.75s;
|
||||
right: -6rem;
|
||||
opacity: 0.1;
|
||||
@ -525,10 +525,6 @@
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
$(window).unload(function () {
|
||||
jQuery.get("/public/logout")
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block js %}
|
||||
|
@ -18,10 +18,10 @@
|
||||
|
||||
<li class="nav-item dropdown user-dropdown">
|
||||
<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-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="font-weight-light text-muted mb-0">Roles: </p>
|
||||
{% 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>
|
||||
</div>
|
||||
</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">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<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>
|
||||
</li>
|
||||
<!-- <li class="nav-item">
|
||||
@ -152,13 +152,13 @@
|
||||
position: absolute;
|
||||
bottom: 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;
|
||||
padding-bottom: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
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*/
|
||||
min-width: 30px;
|
||||
padding-top: 2px;
|
||||
|
@ -121,6 +121,19 @@ data['lang']) }}{% end %}
|
||||
{% end %}
|
||||
</select>
|
||||
</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'] %}
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="manager">{{ translate('userConfig', 'selectManager',
|
||||
|
@ -42,7 +42,6 @@
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<style>
|
||||
.playerItem {
|
||||
background: #1c1e2f;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
@ -711,6 +711,7 @@
|
||||
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
|
||||
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
|
||||
xmlHttpRequest.setRequestHeader('X-Path', path);
|
||||
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', 'server_files')
|
||||
xmlHttpRequest.setRequestHeader('X-Files-Left', left);
|
||||
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
||||
xmlHttpRequest.setRequestHeader('X-ServerId', serverId);
|
||||
|
@ -42,7 +42,7 @@
|
||||
<div class="col-md-12">
|
||||
<div class="input-group">
|
||||
<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>
|
||||
<br />
|
||||
|
@ -145,17 +145,35 @@
|
||||
data['lang']) }}</small> </label>
|
||||
<select id="parent" name="parent" class="form-control form-control-lg select-css"
|
||||
value="{{ data['schedule']['action'] }}">
|
||||
{% for schedule in data['schedules'] %}
|
||||
{% if schedule.schedule_id != data['schedule']['schedule_id'] %}
|
||||
{% if schedule.interval != '' %}
|
||||
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
||||
{{schedule.name}} | {{schedule.command}} | {{schedule.interval}} {{
|
||||
schedule.interval_type}}</option>
|
||||
{% if data['parent'] %}
|
||||
<option id="{{data['parent']['schedule_id']}}" value="{{data['parent']['schedule_id']}}">
|
||||
{{data['parent']['name']}} | {{data['parent']['command']}} | {{data['parent']['interval']}}
|
||||
</option>
|
||||
{% for schedule in data['schedules'] %}
|
||||
{% 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 %}
|
||||
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">
|
||||
{{schedule.name}} | {{schedule.command}} | {{schedule.cron_string}}</option>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% for schedule in data['schedules'] %}
|
||||
{% if schedule.schedule_id != data['schedule']['schedule_id'] and schedule.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 %}
|
||||
{% end %}
|
||||
</select>
|
||||
</div>
|
||||
|
@ -14,7 +14,8 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<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 />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -40,13 +41,21 @@
|
||||
</span>
|
||||
|
||||
<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 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>
|
||||
<br />
|
||||
|
||||
<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">
|
||||
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand',
|
||||
data['lang']) }}</button>
|
||||
@ -54,30 +63,60 @@
|
||||
</div>
|
||||
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
||||
{% 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">
|
||||
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled"><i
|
||||
class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', '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 id="update_control_buttons"
|
||||
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
|
||||
style="visibility: visible">
|
||||
<button onclick="" id="start-btn" style="max-width: 7rem;"
|
||||
class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{
|
||||
translate('serverTerm', '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>
|
||||
{% 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">
|
||||
<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 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">
|
||||
<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>
|
||||
{% 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">
|
||||
<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',
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
{% 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">
|
||||
<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 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">
|
||||
<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>
|
||||
{% end %}
|
||||
{% end %}
|
||||
@ -193,9 +232,12 @@
|
||||
function new_line_handler(data) {
|
||||
$('#virt_console').append(data.line)
|
||||
const elem = document.getElementById('virt_console');
|
||||
const scrollDiff = (elem.scrollHeight - elem.scrollTop) - elem.clientHeight;
|
||||
if (!$("#stop_scroll").is(':checked') && scrollDiff < 450) {
|
||||
scrollConsole()
|
||||
try {
|
||||
if (!scrolled) {
|
||||
scrollConsole();
|
||||
}
|
||||
} catch {
|
||||
scrollConsole();
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,6 +335,30 @@
|
||||
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>
|
||||
|
||||
{% end %}
|
@ -8,7 +8,7 @@
|
||||
{% block content %}
|
||||
<!-- View for Large screen -->
|
||||
<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%;'>
|
||||
<hr />
|
||||
<div class="table-responsive">
|
||||
@ -40,7 +40,8 @@
|
||||
</td>
|
||||
<td id="server_motd_{{ server['stats']['server_id']['server_id'] }}">
|
||||
{% 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">{{
|
||||
server['stats']['desc'] }}</span> <br />
|
||||
{% end %}
|
||||
@ -94,21 +95,25 @@
|
||||
<h2 class="mb-0 container overflow-hidden">
|
||||
<div class="row">
|
||||
<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"
|
||||
data-target="#collapse-{{server['server_data']['server_id']}}" aria-expanded="false"
|
||||
aria-controls="collapse-{{server['server_data']['server_id']}}">
|
||||
<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"
|
||||
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>
|
||||
{{ server['server_data']['server_name'] }}
|
||||
</a>
|
||||
</div>
|
||||
<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'] %}
|
||||
<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>
|
||||
{% 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 %}
|
||||
</a>
|
||||
</div>
|
||||
@ -117,12 +122,13 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
{% if server['stats']['int_ping_results'] != 'False' %}
|
||||
<div id="m_server_motd_{{ server['stats']['server_id']['server_id'] }}" class="media">
|
||||
{% 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 %}
|
||||
<div class="media-body">
|
||||
{% if server['stats']['desc'] != 'False' %}
|
||||
@ -143,7 +149,8 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<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 id="m_server_version_{{ server['stats']['server_id']['server_id'] }}"></div>
|
||||
</div>
|
||||
|
@ -313,7 +313,8 @@
|
||||
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled
|
||||
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
|
||||
}}</button>
|
||||
<button type="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>
|
||||
</div>
|
||||
</div>
|
||||
@ -321,9 +322,154 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 grid-margin">
|
||||
<img id="op_logo" style="filter: grayscale(10%); opacity: .1;" src="../../static/assets/images/logo_small.svg"
|
||||
alt="Crafty logo" />
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<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>
|
||||
@ -343,7 +489,7 @@
|
||||
z-index: 200;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
background-color: #2a2c44;
|
||||
background-color: var(--card-banner-bg);
|
||||
}
|
||||
|
||||
.menu-option {
|
||||
@ -412,6 +558,65 @@
|
||||
|
||||
{% 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_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() {
|
||||
bootbox.confirm({
|
||||
title: "{% raw translate('error', 'eulaTitle', data['lang']) %}",
|
||||
@ -438,6 +643,9 @@
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
$(".tree-reset").on("click", function () {
|
||||
location.href = "/server/bedrock_step1";
|
||||
});
|
||||
document.getElementById("root_files_button").addEventListener("click", function () {
|
||||
if (document.forms["zip"]["server_path"].value != "") {
|
||||
if (document.getElementById('root_files_button').classList.contains('clicked')) {
|
||||
@ -488,7 +696,11 @@
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
$.ajax({
|
||||
@ -582,8 +794,11 @@
|
||||
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);
|
||||
});
|
||||
|
@ -423,7 +423,8 @@
|
||||
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled
|
||||
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
|
||||
}}</button>
|
||||
<button type="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>
|
||||
</div>
|
||||
</div>
|
||||
@ -431,9 +432,172 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 grid-margin">
|
||||
<img id="op_logo" style="filter: grayscale(10%); opacity: .1;" src="../../static/assets/images/logo_small.svg"
|
||||
alt="Crafty logo" />
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<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>
|
||||
@ -457,7 +621,7 @@
|
||||
z-index: 200;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
background-color: #2a2c44;
|
||||
background-color: var(--card-banner-bg);
|
||||
}
|
||||
|
||||
.menu-option {
|
||||
@ -548,271 +712,449 @@
|
||||
} else {
|
||||
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>
|
||||
function dropDown(event) {
|
||||
event.target.parentElement.children[1].classList.remove("d-none");
|
||||
document.getElementById("overlay").classList.remove("d-none");
|
||||
}
|
||||
$(".tree-reset").on("click", function () {
|
||||
location.href = "/server/step1";
|
||||
});
|
||||
function dropDown(event) {
|
||||
event.target.parentElement.children[1].classList.remove("d-none");
|
||||
document.getElementById("overlay").classList.remove("d-none");
|
||||
}
|
||||
|
||||
function hide(event) {
|
||||
var items = document.getElementsByClassName('menu');
|
||||
items.forEach(item => {
|
||||
item.classList.add("d-none");
|
||||
})
|
||||
function hide(event) {
|
||||
var items = document.getElementsByClassName('menu');
|
||||
items.forEach(item => {
|
||||
item.classList.add("d-none");
|
||||
})
|
||||
|
||||
document.getElementById("overlay").classList.add("d-none");
|
||||
}
|
||||
document.getElementById("overlay").classList.add("d-none");
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log('ready');
|
||||
var forms = $('form.server-wizard');
|
||||
forms.each(function (i, formEl) {
|
||||
var form = $(formEl);
|
||||
$(document).ready(function () {
|
||||
console.log('ready');
|
||||
var forms = $('form.server-wizard');
|
||||
forms.each(function (i, formEl) {
|
||||
var form = $(formEl);
|
||||
var min = form.find('[name=min_memory]');
|
||||
var max = form.find('[name=max_memory]');
|
||||
console.log(form, min, max)
|
||||
min.change(function () {
|
||||
check_sizes(max, min, 'min');
|
||||
});
|
||||
});
|
||||
max.change(function () {
|
||||
check_sizes(max, min, 'max');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function wait_msg(importing) {
|
||||
bootbox.alert({
|
||||
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"]) %}',
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
function wait_msg(importing) {
|
||||
bootbox.alert({
|
||||
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"]) %}',
|
||||
});
|
||||
}
|
||||
if (max_mem < min_mem && changed === 'max') {
|
||||
b.val(max_mem)
|
||||
|
||||
function show_file_tree() {
|
||||
$("#dir_select").modal();
|
||||
}
|
||||
}
|
||||
|
||||
function getTreeView(path) {
|
||||
document.getElementById('zip_submit').disabled = false;
|
||||
path = path
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
function getTreeView(path) {
|
||||
//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,
|
||||
dataType: 'text',
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
console.log(data);
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try {
|
||||
document.getElementById('main-tree-div').innerHTML += text;
|
||||
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");
|
||||
try {
|
||||
document.getElementById('main-tree-div').innerHTML += text;
|
||||
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||
} catch {
|
||||
console.log("Bad")
|
||||
document.getElementById('files-tree').innerHTML = text;
|
||||
}
|
||||
|
||||
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");
|
||||
});
|
||||
}
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
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")
|
||||
document.getElementById("refresh-cache").classList.add("fa-spin")
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
|
||||
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 {
|
||||
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',
|
||||
success: function () {
|
||||
document.getElementById("refresh-cache").classList.remove("fa-sync");
|
||||
document.getElementById("refresh-cache").classList.remove("fa-spin");
|
||||
document.getElementById("refresh-cache").classList.add("fa-check");
|
||||
document.getElementById("refresh-cache").classList.remove("fa-spin");
|
||||
document.getElementById("refresh-cache").classList.add("fa-check");
|
||||
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 2000);
|
||||
},
|
||||
});
|
||||
}
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 2000);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var text = '{% raw data["js_server_types"] %}';
|
||||
var serverTypesLists = JSON.parse(text);
|
||||
/* CountryChange() is called from the onchange event of a select element.
|
||||
* param selectObj - the select object which fired the on change event.
|
||||
*/
|
||||
function serverTypeChange(selectObj) {
|
||||
// get the index of the selected option
|
||||
var idx = document.getElementById('server_type').selectedIndex;
|
||||
// get the value of the selected option
|
||||
var cSelect = document.getElementById("server");
|
||||
try {
|
||||
var which = document.getElementById('server_type').options[idx].value;
|
||||
} catch {
|
||||
var text = '{% raw data["js_server_types"] %}';
|
||||
var serverTypesLists = JSON.parse(text);
|
||||
/* CountryChange() is called from the onchange event of a select element.
|
||||
* param selectObj - the select object which fired the on change event.
|
||||
*/
|
||||
function serverTypeChange(selectObj) {
|
||||
// get the index of the selected option
|
||||
var idx = document.getElementById('server_type').selectedIndex;
|
||||
// get the value of the selected option
|
||||
var cSelect = document.getElementById("server");
|
||||
try {
|
||||
var which = document.getElementById('server_type').options[idx].value;
|
||||
} 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) {
|
||||
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) {
|
||||
cSelect.remove(0);
|
||||
}
|
||||
var newOption;
|
||||
// create new options ordered by ascending
|
||||
cList[server].forEach(type => {
|
||||
newOption = document.createElement("option");
|
||||
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.text = type;
|
||||
// add the new option
|
||||
try {
|
||||
cSelect.add(newOption); // this will fail in DOM browsers but is needed for IE
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
cSelect.appendChild(newOption);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function serverJarChange(selectObj) {
|
||||
let type_select = document.getElementById('server_jar')
|
||||
let tidx = type_select.selectedIndex;
|
||||
let val = type_select.options[tidx].value;
|
||||
if (val == 'None') {
|
||||
function serverJarChange(selectObj) {
|
||||
let type_select = document.getElementById('server_jar')
|
||||
let tidx = type_select.selectedIndex;
|
||||
let val = type_select.options[tidx].value;
|
||||
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");
|
||||
// remove the current options from the country select
|
||||
var jlen = jcSelect.options.length;
|
||||
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");
|
||||
// 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");
|
||||
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.text = type;
|
||||
// add the new option
|
||||
try {
|
||||
jcSelect.add(jnewOption); // this will fail in DOM browsers but is needed for IE
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
jcSelect.appendChild(jnewOption);
|
||||
}
|
||||
})
|
||||
serverTypeChange(selectObj);
|
||||
}
|
||||
}
|
||||
})
|
||||
serverTypeChange(selectObj);
|
||||
}
|
||||
</script>
|
||||
{% 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",
|
||||
"addRole": "Server zu existierender Rolle hinzufügen",
|
||||
"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!",
|
||||
"clickRoot": "Hier klicken, um das Stammverzeichnis auszuwählen",
|
||||
"close": "Schließen",
|
||||
|
@ -359,7 +359,8 @@
|
||||
"schedule": "Schedule",
|
||||
"serverDetails": "Server Details",
|
||||
"terminal": "Terminal",
|
||||
"metrics": "Metrics"
|
||||
"metrics": "Metrics",
|
||||
"reset": "Reset Scroll"
|
||||
},
|
||||
"serverFiles": {
|
||||
"clickUpload": "Click here to select your files",
|
||||
@ -501,6 +502,7 @@
|
||||
"importServer": "Import an Existing Server",
|
||||
"importServerButton": "Import Server!",
|
||||
"importZip": "Import from a Zip File",
|
||||
"uploadZip": "Upload Zip File For Server Import",
|
||||
"maxMem": "Maximum Memory",
|
||||
"minMem": "Minimum Memory",
|
||||
"myNewServer": "My New Server",
|
||||
@ -564,6 +566,7 @@
|
||||
"roleName": "Role Name",
|
||||
"super": "Super User",
|
||||
"userLang": "User Language",
|
||||
"userTheme": "UI Theme",
|
||||
"userName": "User Name",
|
||||
"userNameDesc": "What do you want to call this user?",
|
||||
"userRoles": "User Roles",
|
||||
|
Loading…
Reference in New Issue
Block a user