Merge branch 'dev' into 'master'

v4.4.5

See merge request crafty-controller/crafty-4!822
This commit is contained in:
Iain Powrie 2025-01-19 20:44:53 +00:00
commit 2fd4dfa128
137 changed files with 37581 additions and 3945 deletions

View File

@ -5,8 +5,7 @@
stages:
- lint
- test
- prod-deployment
- dev-deployment
- build-and-deploy
- release
variables:

View File

@ -1,81 +1,42 @@
# yamllint disable rule:line-length
---
docker-build-dev:
docker-build:
image: docker:latest
services:
- name: docker:dind
stage: dev-deployment
stage: build-and-deploy
tags:
- docker_priv
rules:
- if: $CI_COMMIT_BRANCH == 'dev'
environment:
name: development
before_script:
- |
apk --no-cache add jq
MAJOR=$(cat app/config/version.json | jq '.major' )
MINOR=$(cat app/config/version.json | jq '.minor' )
SUB=$(cat app/config/version.json | jq '.sub' )
- |
apk --no-cache add curl
latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p')
echo "Using buildx version $latest_tag"
curl -sSLo docker-buildx "https://github.com/docker/buildx/releases/download/$latest_tag/buildx-$latest_tag.linux-amd64"
chmod a+x docker-buildx
mkdir -p ~/.docker/cli-plugins
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
docker version
- docker run --rm --privileged aptman/qus -- -r
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
- echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
script:
- |
tag=":$CI_COMMIT_REF_SLUG"
VERSION="${MAJOR}.${MINOR}.${SUB}"
- |
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
echo "Crafty Version: $VERSION"
- docker context create tls-environment
- docker buildx create --name zedBuilder --use tls-environment
- docker buildx build
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
--build-arg BUILDKIT_INLINE_CACHE=1
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}"
--provenance false
--tag "$CI_REGISTRY_IMAGE${tag}"
--tag "arcadiatechnology/crafty-4${tag}"
--platform linux/arm64/v8,linux/amd64
--push .
after_script:
- |
docker buildx rm zedBuilder && echo "Successfully Stopped builder instance" || echo "Failed to stop builder instance."
docker context rm tls-environment || true
echo "Please review multi-arch manifests are present:"
docker buildx imagetools inspect "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
docker-build-prod:
image: docker:latest
services:
- name: docker:dind
stage: prod-deployment
tags:
- docker_priv
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Development branch
- if: $CI_COMMIT_BRANCH == 'dev'
variables:
ENVIRONMENT_NAME: "development"
DOCKER_TAGS: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG arcadiatechnology/crafty-4:$CI_COMMIT_REF_SLUG"
# Production branch (main) when not scheduled
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE != "schedule"
variables:
ENVIRONMENT_NAME: "production"
# We'll set the version in the script and append stable tags
DOCKER_TAGS: "" # We'll determine in script
# Scheduled nightly builds on main
- if: $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
ENVIRONMENT_NAME: "nightly"
DOCKER_TAGS: "$CI_REGISTRY_IMAGE:nightly arcadiatechnology/crafty-4:nightly"
environment:
name: production
name: $ENVIRONMENT_NAME
before_script:
- |
apk --no-cache add jq
MAJOR=$(cat app/config/version.json | jq '.major' )
MINOR=$(cat app/config/version.json | jq '.minor' )
SUB=$(cat app/config/version.json | jq '.sub' )
apk --no-cache add jq curl
MAJOR=$(cat app/config/version.json | jq -r '.major')
MINOR=$(cat app/config/version.json | jq -r '.minor')
SUB=$(cat app/config/version.json | jq -r '.sub')
VERSION="${MAJOR}.${MINOR}.${SUB}"
- |
apk --no-cache add curl
latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p')
echo "Using buildx version $latest_tag"
curl -sSLo docker-buildx "https://github.com/docker/buildx/releases/download/$latest_tag/buildx-$latest_tag.linux-amd64"
@ -87,30 +48,61 @@ docker-build-prod:
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
- echo $CI_JOB_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
- docker context create tls-environment-$CI_JOB_ID
- docker buildx create --name zedBuilder-$CI_JOB_ID --use tls-environment-$CI_JOB_ID
script:
- echo "Running on branch '$CI_COMMIT_BRANCH', environment- $ENVIRONMENT_NAME"
- echo "Crafty Version- $VERSION"
# If DOCKER_TAGS is empty (production default branch and not schedule), we set stable tags:
# This scenario adds both VERSION and 'latest' tags.
- |
VERSION="${MAJOR}.${MINOR}.${SUB}"
if [ "$ENVIRONMENT_NAME" = "production" ] && [ -z "$DOCKER_TAGS" ]; then
DOCKER_TAGS="$CI_REGISTRY_IMAGE:${VERSION} \
$CI_REGISTRY_IMAGE:latest \
arcadiatechnology/crafty-4:${VERSION} \
arcadiatechnology/crafty-4:latest"
fi
- echo "Using the following tags- $DOCKER_TAGS"
# Prepare build command
# We break DOCKER_TAGS into separate --tag arguments
- TARGS=""
- for t in $DOCKER_TAGS; do TARGS="$TARGS --tag $t"; done
- echo "Tag arguments- $TARGS"
# Conditional build command: omit cache if schedule
- |
echo "Running on branch '$CI_COMMIT_BRANCH'"
echo "Crafty Version: $VERSION"
- docker context create tls-environment
- docker buildx create --name zedBuilder --use tls-environment
- docker buildx build
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE:latest"
--build-arg BUILDKIT_INLINE_CACHE=1
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}"
--provenance false
--tag "$CI_REGISTRY_IMAGE:$VERSION"
--tag "$CI_REGISTRY_IMAGE:latest"
--tag "arcadiatechnology/crafty-4:$VERSION"
--tag "arcadiatechnology/crafty-4:latest"
--platform linux/arm64/v8,linux/amd64
if [ "$CI_PIPELINE_SOURCE" = "schedule" ]; then
echo "Omitting cache for nightly build."
docker buildx build \
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")" \
--build-arg "BUILD_REF=${CI_COMMIT_SHA}" \
--build-arg "CRAFTY_VER=${VERSION}" \
--provenance false \
$TARGS \
--platform linux/arm64/v8,linux/amd64 \
--push .
else
echo "Using cache for build."
docker buildx build \
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE:dev" \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")" \
--build-arg "BUILD_REF=${CI_COMMIT_SHA}" \
--build-arg "CRAFTY_VER=${VERSION}" \
--provenance false \
$TARGS \
--platform linux/arm64/v8,linux/amd64 \
--push .
fi
after_script:
- |
docker buildx rm zedBuilder && echo "Successfully Stopped builder instance" || echo "Failed to stop builder instance."
docker context rm tls-environment || true
echo "Please review multi-arch manifests are present:"
docker buildx imagetools inspect "$CI_REGISTRY_IMAGE${tag}"
- docker buildx rm zedBuilder-$CI_JOB_ID && echo "Successfully Stopped builder instance" || echo "Failed to stop builder instance."
- docker context rm tls-environment-$CI_JOB_ID || true
- echo "Please review multi-arch manifests are present:"
- if [ "$ENVIRONMENT_NAME" = "development" ]; then docker buildx imagetools inspect "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"; fi
- if [ "$ENVIRONMENT_NAME" = "production" ] && [ -n "$VERSION" ]; then docker buildx imagetools inspect "$CI_REGISTRY_IMAGE:$VERSION"; fi
- if [ "$ENVIRONMENT_NAME" = "nightly" ]; then docker buildx imagetools inspect "$CI_REGISTRY_IMAGE:nightly"; fi

View File

@ -9,6 +9,8 @@ yamllint:
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
script:
- yamllint .
@ -22,6 +24,8 @@ jsonlint:
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
script:
- |
@ -37,6 +41,8 @@ black:
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
script:
- black --check --verbose -- .
@ -50,10 +56,12 @@ pylint:
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
before_script:
- apk update
- apk add gcc python3-dev linux-headers build-base
- apk add gcc python3-dev linux-headers build-base rust cargo
- pip3 install --no-cache-dir -r requirements.txt
script:
- pylint --exit-zero --load-plugins=pylint_gitlab --output-format=gitlab-codeclimate:codeclimate.json $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**")
@ -75,6 +83,8 @@ sonarcloud-check:
when: never
- if: "$CODE_QUALITY_DISABLED"
when: never
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
@ -85,26 +95,28 @@ sonarcloud-check:
- .sonar/cache
script:
- sonar-scanner
# Lang file checking
lang-check:
stage: lint
image: alpine:latest
tags:
- saas-linux-medium-amd64
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
- if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
allow_failure: true
before_script:
- apk add --no-cache jq bash
script:
- chmod +x .gitlab/scripts/lang_sort.sh
- bash .gitlab/scripts/lang_sort.sh ./app/translations/
after_script:
- if [ -f .gitlab/scripts/lang_sort_log.txt ]; then cat .gitlab/scripts/lang_sort_log.txt; fi
artifacts:
paths:
- .gitlab/scripts/lang_sort_log.txt
expire_in: 1 week
# lang-check:
# stage: lint
# image: alpine:latest
# tags:
# - saas-linux-medium-amd64
# rules:
# - if: "$CODE_QUALITY_DISABLED"
# when: never
# - if: $CI_PIPELINE_SOURCE == "schedule"
# when: never
# - if: "$CI_COMMIT_TAG || $CI_COMMIT_BRANCH"
# allow_failure: true
# before_script:
# - apk add --no-cache jq bash
# script:
# - chmod +x .gitlab/scripts/lang_sort.sh
# - bash .gitlab/scripts/lang_sort.sh ./app/translations/
# after_script:
# - if [ -f .gitlab/scripts/lang_sort_log.txt ]; then cat .gitlab/scripts/lang_sort_log.txt; fi
# artifacts:
# paths:
# - .gitlab/scripts/lang_sort_log.txt
# expire_in: 1 week
# DISABLED - As Weblate managed.

View File

@ -4,6 +4,8 @@ release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: $CI_COMMIT_TAG
needs:
- job: win-prod-build

View File

@ -1,13 +1,15 @@
# yamllint disable rule:line-length
---
win-dev-build:
stage: dev-deployment
stage: build-and-deploy
tags:
- win64
cache:
paths:
- .venv/
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: "$CI_COMMIT_BRANCH == 'dev'"
environment:
name: development
@ -48,13 +50,15 @@ win-dev-build:
- app\classes\**\*
win-prod-build:
stage: prod-deployment
stage: build-and-deploy
tags:
- win64
cache:
paths:
- .venv/
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
- if: $CI_COMMIT_TAG
environment:

View File

@ -1,4 +1,27 @@
# Changelog
## --- [4.4.5] - 2024/01/19
### Refactor
- Refactor and standardize all JSON validator errors returning human readable translations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/786))
- Improve docker-build CI/CD, supporting nightly builds ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/813))
- Standardize and centralize CSS throughout front end, Allows for easier management of themes ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/726))
### Bug fixes
- Bump requests to resolve yank for CVE-2024-35195 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/808))
- Better handle malformed mcping data ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/799))
- Resolve type issue when posting no keywords in the "keyword" field in config.json tab ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/819))
- Resolve issue where sometimes backup migration `20240308_multi-backup` would run twice ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/820))
### Tweaks
- Dyamically change child action translation for backups ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/806))
- Remove EXIF image data on app Background Photos ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/805))
- Bump Docker base image `22.04` -> `24.04` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/812))
- Bump python pip `2.0.3` -> `24.3.1` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/812))
- Bump python setuptools `50.3.2` -> `75.6.0` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/812))
- Bump tornado for CVE-2024-52804 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/814))
### Lang
- Weblate Translation Platform Integration
- Remove incomplete labels from translation files to better support new translation workflow ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/810))
- New langs added `ja_JP`, `ko_KR` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/811))
<br><br>
## --- [4.4.4] - 2024/10/03
### Bug fixes
- Migrations | Fix orphan schedule configurations crashing migration operation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/796))

View File

@ -1,4 +1,4 @@
FROM ubuntu:22.04
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND="noninteractive"
@ -36,7 +36,7 @@ WORKDIR /crafty
COPY --chown=crafty:root requirements.txt ./
RUN python3 -m venv ./.venv \
&& . .venv/bin/activate \
&& pip3 install --no-cache-dir --upgrade setuptools==50.3.2 pip==22.0.3 \
&& pip3 install --no-cache-dir --upgrade setuptools==75.6.0 pip==24.3.1 \
&& pip3 install --no-cache-dir -r requirements.txt \
&& deactivate
USER root

View File

@ -1,5 +1,5 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
# Crafty Controller 4.4.4
# Crafty Controller 4.4.5
> Python based Control Panel for your Minecraft Server
## What is Crafty Controller?

View File

@ -37,9 +37,16 @@ class UsersController:
permission.name
for permission in PermissionsCrafty.get_permissions_list()
],
"error": "enumErr",
"fill": True,
},
"quantity": {"type": "number", "minimum": -1},
"enabled": {"type": "boolean"},
"quantity": {
"type": "number",
"minimum": -1,
"error": "typeInteger",
"fill": True,
},
"enabled": {"type": "boolean", "error": "typeBool", "fill": True},
}
self.user_jsonschema_props: t.Final = {
"username": {
@ -49,6 +56,8 @@ class UsersController:
"pattern": "^[a-z0-9_]+$",
"examples": ["admin"],
"title": "Username",
"error": "userName",
"fill": True,
},
"password": {
"type": "string",
@ -62,11 +71,15 @@ class UsersController:
"format": "email",
"examples": ["default@example.com"],
"title": "E-Mail",
"error": "typeEmail",
"fill": True,
},
"enabled": {
"type": "boolean",
"examples": [True],
"title": "Enabled",
"error": "typeBool",
"fill": True,
},
"lang": {
"type": "string",
@ -74,16 +87,30 @@ class UsersController:
"minLength": 2,
"examples": ["en"],
"title": "Language",
"error": "typeString",
"fill": True,
},
"superuser": {
"type": "boolean",
"examples": [False],
"title": "Superuser",
"error": "typeBool",
"fill": True,
},
"manager": {
"type": ["integer", "null"],
"error": "typeInteger",
"fill": True,
},
"theme": {
"type": "string",
"error": "typeString",
"fill": True,
},
"manager": {"type": ["integer", "null"]},
"theme": {"type": "string"},
"permissions": {
"type": "array",
"error": "typeList",
"fill": True,
"items": {
"type": "object",
"properties": _permissions_props,
@ -92,13 +119,25 @@ class UsersController:
},
"roles": {
"type": "array",
"error": "typeList",
"fill": True,
"items": {
"type": "integer",
"minLength": 1,
"error": "typeInteger",
"fill": True,
},
},
"hints": {"type": "boolean"},
"server_order": {"type": "string"},
"hints": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"server_order": {
"type": "string",
"error": "typeString",
"fill": True,
},
}
# **********************************************************************************

View File

@ -77,8 +77,8 @@ class Server:
class Players(list):
def __init__(self, data):
super().__init__(Player(x) for x in data.get("sample", []))
self.max = data["max"]
self.online = data["online"]
self.max = data.get("max", 0)
self.online = data.get("online", 0)
def report(self):
players = []
@ -93,8 +93,8 @@ class Players(list):
class Player:
def __init__(self, data):
self.id = data["id"]
self.name = data["name"]
self.id = data.get("id", "")
self.name = data("name", "Anonymous")
def __str__(self):
return self.name
@ -174,7 +174,7 @@ def ping(ip, port):
logger.debug(f"Server reports this data on ping: {data}")
try:
return Server(json.loads(data))
except KeyError:
except (KeyError, json.decoder.JSONDecodeError):
return {}
finally:
sock.close()

View File

@ -85,6 +85,8 @@ class Helpers:
self.crafty_starting = False
self.minimum_password_length = 8
self.theme_list = self.load_themes()
@staticmethod
def auto_installer_fix(ex):
logger.critical(f"Import Error: Unable to load {ex.name} module", exc_info=True)
@ -595,9 +597,20 @@ class Helpers:
)
return False
@staticmethod
def get_themes():
return ["default", "dark", "light", "ronald"]
def load_themes(self):
theme_list = []
themes_path = os.path.join(self.webroot, "static", "assets", "css", "themes")
theme_files = [
file
for file in os.listdir(themes_path)
if os.path.isfile(os.path.join(themes_path, file))
]
for theme in theme_files:
theme_list.append(theme.split(".css")[0])
return theme_list
def get_themes(self):
return self.theme_list
@staticmethod
def get_local_ip():
@ -1198,8 +1211,8 @@ class Helpers:
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
<input type="radio" name="root_path" value="{dpath}">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
<i class="text-info far fa-folder"></i>
<i class="text-info far fa-folder-open"></i>
{filename}
</span>
</input></div><li>
@ -1220,8 +1233,8 @@ class Helpers:
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
<input type="radio" name="root_path" value="{dpath}">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
<i class="text-info far fa-folder"></i>
<i class="text-info far fa-folder-open"></i>
{filename}
</span>
</input></div><li>"""

View File

@ -23,9 +23,9 @@ class Translation:
def translate(self, page, word, language, error=True):
fallback_language = "en_EN"
translated_word = self.translate_inner(page, word, language)
translated_word = self.translate_inner(page, word, language, error)
if translated_word is None:
translated_word = self.translate_inner(page, word, fallback_language)
translated_word = self.translate_inner(page, word, fallback_language, error)
if translated_word:
if isinstance(translated_word, dict):
@ -41,7 +41,7 @@ class Translation:
return "Error while getting translation"
return word
def translate_inner(self, page, word, language) -> t.Union[t.Any, None]:
def translate_inner(self, page, word, language, error) -> t.Union[t.Any, None]:
language_file = self.get_language_file(language)
try:
if not self.cached_translation:
@ -59,6 +59,7 @@ class Translation:
try:
translated_page = data[page]
except KeyError:
if error:
logger.error(
f"Translation File Error: page {page} "
f"does not exist for lang {language}"
@ -73,6 +74,7 @@ class Translation:
translated_word = translated_page[word]
return translated_word
except KeyError:
if error:
logger.error(
f"Translation File Error: word {word} does not exist on page "
f"{page} for lang {language}"
@ -84,6 +86,7 @@ class Translation:
return None
except Exception as e:
if error:
logger.critical(
f"Translation File Error: Unable to read {language_file} due to {e}"
)

View File

@ -11,7 +11,14 @@ class BaseApiHandler(BaseHandler):
# {{{ 405 Method Not Allowed as JSON
def _unimplemented_method(self, *_args: str, **_kwargs: str) -> None:
self.finish_json(405, {"status": "error", "error": "METHOD_NOT_ALLOWED"})
self.finish_json(
405,
{
"status": "error",
"error": "METHOD_NOT_ALLOWED",
"error_data": "METHOD NOT ALLOWED",
},
)
head = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
get = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]

View File

@ -43,6 +43,8 @@ SUBPAGE_PERMS = {
SCHEDULE_AUTH_ERROR_URL = "/panel/error?error=Unauthorized access To Schedules"
HUMANIZED_INDEX_FILE = "humanized_index.json"
class PanelHandler(BaseHandler):
def get_user_roles(self) -> t.Dict[str, list]:
@ -359,6 +361,7 @@ class PanelHandler(BaseHandler):
else None
),
"superuser": superuser,
"themes": self.helper.get_themes(),
}
try:
page_data["hosts_data"]["disk_json"] = json.loads(
@ -879,7 +882,7 @@ class PanelHandler(BaseHandler):
os.path.join(self.helper.root_dir, "app", "translations")
)
):
if file == "humanized_index.json":
if file == HUMANIZED_INDEX_FILE:
continue
if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting(
@ -977,6 +980,8 @@ class PanelHandler(BaseHandler):
for file in sorted(
os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
):
if file == HUMANIZED_INDEX_FILE:
continue
if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
@ -1434,7 +1439,7 @@ class PanelHandler(BaseHandler):
for file in sorted(
os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
):
if file == "humanized_index.json":
if file == HUMANIZED_INDEX_FILE:
continue
if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting(

View File

@ -45,6 +45,7 @@ class PublicHandler(BaseHandler):
"query": "",
"background": self.controller.cached_login,
"login_opacity": self.controller.management.get_login_opacity(),
"themes": self.helper.get_themes(),
}
if self.request.query:
@ -261,7 +262,11 @@ class PublicHandler(BaseHandler):
)
return self.finish_json(
403,
{"status": "error", "error": error_msg},
{
"status": "error",
"error": "INVALID CREDENTIALS",
"error_data": error_msg,
},
)
else:
self.redirect("/login?")

View File

@ -68,7 +68,12 @@ class ApiAuthLoginHandler(BaseApiHandler):
)
return self.finish_json(
401,
{"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None},
{
"status": "error",
"error": "INCORRECT_CREDENTIALS",
"error_data": "INVALID CREDENTIALS",
"token": None,
},
)
if not user_data.enabled:
@ -78,7 +83,13 @@ class ApiAuthLoginHandler(BaseApiHandler):
f" IP {self.get_remote_ip()} account disabled"
)
self.finish_json(
403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None}
403,
{
"status": "error",
"error": "ACCOUNT_DISABLED",
"error_data": "ACCOUNT DISABLED",
"token": None,
},
)
return
@ -123,5 +134,9 @@ class ApiAuthLoginHandler(BaseApiHandler):
)
self.finish_json(
401,
{"status": "error", "error": "INCORRECT_CREDENTIALS"},
{
"status": "error",
"error": "INCORRECT_CREDENTIALS",
"error_data": "INCORRECT CREDENTIALS",
},
)

View File

@ -115,7 +115,14 @@ class ApiAnnounceIndexHandler(BaseApiHandler):
if str(data["id"]) in str(res):
cleared_notifs.append(data["id"])
else:
self.finish_json(200, {"status": "error", "error": "INVALID_DATA"})
self.finish_json(
200,
{
"status": "error",
"error": "INVALID_DATA",
"error_data": "INVALID NOTIFICATION ID",
},
)
return
updata = {"cleared_notifs": ",".join(cleared_notifs)}
self.controller.users.update_user(auth_data[4]["user_id"], updata)

View File

@ -18,7 +18,16 @@ class ApiCraftyLogIndexHandler(BaseApiHandler):
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
log_types = ["audit", "session", "schedule"]
if log_type not in log_types:

View File

@ -9,29 +9,111 @@ from app.classes.web.base_api_handler import BaseApiHandler
config_json_schema = {
"type": "object",
"properties": {
"https_port": {"type": "integer"},
"https_port": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"language": {
"type": "string",
"error": "typeString",
"fill": True,
},
"cookie_expire": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"show_errors": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"history_max_age": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"stats_update_frequency_seconds": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"delete_default_json": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"show_contribute_link": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"virtual_terminal_lines": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"max_log_lines": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"max_audit_entries": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"disabled_language_files": {
"type": "array",
"error": "typeList",
"fill": True,
},
"stream_size_GB": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"keywords": {
"type": "array",
"error": "typeList",
"fill": True,
},
"allow_nsfw_profile_pictures": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"enable_user_self_delete": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"reset_secrets_on_next_boot": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"monitored_mounts": {
"type": "array",
"error": "typeList",
"fill": True,
},
"dir_size_poll_freq_minutes": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"crafty_logs_delete_after_days": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"big_bucket_repo": {
"type": "string",
"error": "typeString",
"fill": True,
},
"cookie_expire": {"type": "integer"},
"show_errors": {"type": "boolean"},
"history_max_age": {"type": "integer"},
"stats_update_frequency_seconds": {"type": "integer"},
"delete_default_json": {"type": "boolean"},
"show_contribute_link": {"type": "boolean"},
"virtual_terminal_lines": {"type": "integer"},
"max_log_lines": {"type": "integer"},
"max_audit_entries": {"type": "integer"},
"disabled_language_files": {"type": "array"},
"stream_size_GB": {"type": "integer"},
"keywords": {"type": "array"},
"allow_nsfw_profile_pictures": {"type": "boolean"},
"enable_user_self_delete": {"type": "boolean"},
"reset_secrets_on_next_boot": {"type": "boolean"},
"monitored_mounts": {"type": "array"},
"dir_size_poll_freq_minutes": {"type": "integer"},
"crafty_logs_delete_after_days": {"type": "integer"},
"big_bucket_repo": {"type": "string"},
},
"additionalProperties": False,
"minProperties": 1,
@ -39,8 +121,16 @@ config_json_schema = {
customize_json_schema = {
"type": "object",
"properties": {
"photo": {"type": "string"},
"opacity": {"type": "string"},
"photo": {
"type": "string",
"error": "typeString",
"fill": True,
},
"opacity": {
"type": "string",
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -49,7 +139,11 @@ customize_json_schema = {
photo_delete_schema = {
"type": "object",
"properties": {
"photo": {"type": "string"},
"photo": {
"type": "string",
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -75,7 +169,16 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler):
get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200,
@ -98,24 +201,42 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler):
(_, _, _, superuser, user, _) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = orjson.loads(self.request.body)
except orjson.JSONDecodeError as e:
except orjson.JSONDecodeError as why:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
400,
{"status": "error", "error": "INVALID_JSON", "error_data": str(why)},
)
try:
validate(data, config_json_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
@ -152,7 +273,16 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200,
@ -181,7 +311,16 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
_,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = orjson.loads(self.request.body)
@ -247,7 +386,16 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
return
if not auth_data[4]["superuser"]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)

View File

@ -6,7 +6,7 @@ from app.classes.web.base_api_handler import BaseApiHandler
server_dir_schema = {
"type": "object",
"properties": {
"new_dir": {"type": "string"},
"new_dir": {"type": "string", "error": "typeString"},
},
"additionalProperties": False,
"minProperties": 1,
@ -31,7 +31,16 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler):
get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200,
@ -61,29 +70,52 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler):
) = auth_data
if not auth_data:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": "NOT AUTHORIZED",
},
)
if not auth_data[4]["superuser"]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if self.helper.is_env_docker():
raise NotImplementedError
try:
data = orjson.loads(self.request.body)
except orjson.JSONDecodeError as e:
except orjson.JSONDecodeError as why:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
400,
{"status": "error", "error": "INVALID_JSON", "error_data": str(why)},
)
try:
validate(data, server_dir_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = why.path[0] if why.path else None
err = f"""{self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {offending_key}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if self.helper.dir_migration:

View File

@ -16,7 +16,14 @@ class ApiCraftyJarCacheIndexHandler(BaseApiHandler):
) = auth_data
if not auth_data[4]["superuser"]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": "NOT A SUPER USER",
},
)
self.controller.big_bucket.manual_refresh_cache()
self.finish_json(

View File

@ -13,10 +13,25 @@ logger = logging.getLogger(__name__)
files_get_schema = {
"type": "object",
"properties": {
"page": {"type": "string", "minLength": 1},
"folder": {"type": "string"},
"upload": {"type": "boolean", "default": "False"},
"unzip": {"type": "boolean", "default": "True"},
"page": {
"type": "string",
"minLength": 1,
"error": "filesPageLen",
"fill": True,
},
"folder": {"type": "string", "error": "typeString", "fill": True},
"upload": {
"type": "boolean",
"default": "False",
"error": "typeBool",
"fill": True,
},
"unzip": {
"type": "boolean",
"default": "True",
"error": "typeBool",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -37,7 +52,14 @@ class ApiImportFilesIndexHandler(BaseApiHandler):
and not auth_data[4]["superuser"]
):
# if the user doesn't have Files or Backup permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": "INSUFFICEN PERMISSIONS",
},
)
try:
data = json.loads(self.request.body)
@ -47,13 +69,21 @@ class ApiImportFilesIndexHandler(BaseApiHandler):
)
try:
validate(data, files_get_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
# TODO: limit some columns for specific permissions?

View File

@ -1,6 +1,7 @@
import os
import logging
import shutil
from PIL import Image
from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.shared.helpers import Helpers
from app.classes.web.base_api_handler import BaseApiHandler
@ -47,7 +48,14 @@ class ApiFilesUploadHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(
400, {"status": "error", "error": "NOT_AUTHORIZED"}
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
@ -60,7 +68,14 @@ class ApiFilesUploadHandler(BaseApiHandler):
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(
400, {"status": "error", "error": "NOT_AUTHORIZED"}
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
u_type = "server_upload"
@ -111,9 +126,9 @@ class ApiFilesUploadHandler(BaseApiHandler):
try:
file_size = int(self.request.headers.get("fileSize", None))
total_chunks = int(self.request.headers.get("totalChunks", 0))
except TypeError:
except TypeError as why:
return self.finish_json(
400, {"status": "error", "error": "TYPE ERROR", "data": {}}
400, {"status": "error", "error": "TYPE ERROR", "error_data": {why}}
)
self.chunk_index = self.request.headers.get("chunkId")
if u_type == "server_upload":
@ -281,6 +296,21 @@ class ApiFilesUploadHandler(BaseApiHandler):
with open(chunk_file, "rb") as infile:
outfile.write(infile.read())
os.remove(chunk_file)
if upload_type == "background":
# Strip EXIF data
image_path = os.path.join(file_path)
logger.debug("Stripping exif data from image")
image = Image.open(image_path)
# Get current raw pixel data from image
image_data = list(image.getdata())
# Create new image
image_no_exif = Image.new(image.mode, image.size)
# Restore pixel data
image_no_exif.putdata(image_data)
image_no_exif.save(image_path)
logger.info(
f"File upload completed. Filename: {self.filename}"
f" Path: {file_path} Type: {u_type}"

View File

@ -6,7 +6,12 @@ class ApiNotFoundHandler(BaseApiHandler):
def _not_found(self, page: str) -> None:
self.finish_json(
404,
{"status": "error", "error": "API_HANDLER_NOT_FOUND", "page": page},
{
"status": "error",
"error": "API_HANDLER_NOT_FOUND",
"error_data": f"{page} not found",
"page": page,
},
)
head = _not_found # type: Callable[..., Optional[Awaitable[None]]]

View File

@ -12,25 +12,30 @@ create_role_schema = {
"type": "string",
"minLength": 1,
"pattern": r"^[^,\[\]]*$",
"error": "roleName",
},
"servers": {
"type": "array",
"error": "typeList",
"fill": True,
"items": {
"type": "object",
"properties": {
"server_id": {
"type": "string",
"minimum": 1,
"error": "roleServerId",
},
"permissions": {
"type": "string",
"pattern": r"^[01]{8}$", # 8 bits, see EnumPermissionsServer
"error": "roleServerPerms",
},
},
"required": ["server_id", "permissions"],
},
},
"manager": {"type": ["integer", "null"]},
"manager": {"type": ["integer", "null"], "error": "roleManager"},
},
"additionalProperties": False,
"minProperties": 1,
@ -42,19 +47,24 @@ basic_create_role_schema = {
"name": {
"type": "string",
"minLength": 1,
"error": "roleName",
},
"servers": {
"type": "array",
"error": "typeList",
"fill": True,
"items": {
"type": "object",
"properties": {
"server_id": {
"type": "string",
"minimum": 1,
"error": "roleServerId",
},
"permissions": {
"type": "string",
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
"pattern": r"^[01]{8}$", # 8 bits, see EnumPermissionsServer
"error": "roleServerPerms",
},
},
"required": ["server_id", "permissions"],
@ -87,7 +97,16 @@ class ApiRolesIndexHandler(BaseApiHandler):
not superuser
and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200,
@ -120,7 +139,16 @@ class ApiRolesIndexHandler(BaseApiHandler):
not superuser
and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = orjson.loads(self.request.body)
@ -134,13 +162,21 @@ class ApiRolesIndexHandler(BaseApiHandler):
validate(data, create_role_schema)
else:
validate(data, basic_create_role_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
@ -165,7 +201,12 @@ class ApiRolesIndexHandler(BaseApiHandler):
if self.controller.roles.get_roleid_by_name(role_name) is not None:
return self.finish_json(
400, {"status": "error", "error": "ROLE_NAME_ALREADY_EXISTS"}
400,
{
"status": "error",
"error": "ROLE_NAME_ALREADY_EXISTS",
"error_data": "UNIQUE VALUE ERROR",
},
)
role_id = self.controller.roles.add_role_advanced(role_name, servers, manager)

View File

@ -11,25 +11,30 @@ modify_role_schema = {
"type": "string",
"minLength": 1,
"pattern": r"^[^,\[\]]*$",
"error": "roleName",
},
"servers": {
"type": "array",
"error": "typeList",
"fill": True,
"items": {
"type": "object",
"properties": {
"server_id": {
"type": "string",
"minimum": 1,
"error": "roleServerId",
},
"permissions": {
"type": "string",
"pattern": r"^[01]{8}$", # 8 bits, see EnumPermissionsServer
"error": "roleServerPerms",
},
},
"required": ["server_id", "permissions"],
},
},
"manager": {"type": ["integer", "null"]},
"manager": {"type": ["integer", "null"], "error": "roleManager"},
},
"additionalProperties": False,
"minProperties": 1,
@ -41,19 +46,24 @@ basic_modify_role_schema = {
"name": {
"type": "string",
"minLength": 1,
"error": "roleName",
},
"servers": {
"type": "array",
"error": "typeList",
"fill": True,
"items": {
"type": "object",
"properties": {
"server_id": {
"type": "string",
"minimum": 1,
"error": "roleServerId",
},
"permissions": {
"type": "string",
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
"pattern": r"^[01]{8}$", # 8 bits, see EnumPermissionsServer
"error": "roleServerPerms",
},
},
"required": ["server_id", "permissions"],
@ -83,15 +93,26 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
not superuser
and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
self.finish_json(
200,
{"status": "ok", "data": self.controller.roles.get_role(role_id)},
)
except DoesNotExist:
self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
except DoesNotExist as why:
self.finish_json(
404, {"status": "error", "error": "ROLE_NOT_FOUND", "error_data": why}
)
def delete(self, role_id: str):
auth_data = self.authenticate_user()
@ -110,7 +131,16 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
str(role.get("manager", "no manager found")) != str(auth_data[4]["user_id"])
and not superuser
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.controller.roles.remove_role(role_id)
@ -165,13 +195,21 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
validate(data, modify_role_schema)
else:
validate(data, basic_modify_role_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
@ -188,11 +226,13 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
data.get("servers", None),
manager,
)
except DoesNotExist:
return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
except IntegrityError:
except DoesNotExist as why:
return self.finish_json(
404, {"status": "error", "error": "ROLE_NAME_EXISTS"}
404, {"status": "error", "error": "ROLE_NOT_FOUND", "error_data": why}
)
except IntegrityError as why:
return self.finish_json(
404, {"status": "error", "error": "ROLE_NAME_EXISTS", "error_data": why}
)
self.controller.management.add_to_audit_log(
user["user_id"],

View File

@ -20,7 +20,16 @@ class ApiRolesRoleServersHandler(BaseApiHandler):
get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200,

View File

@ -16,7 +16,16 @@ class ApiRolesRoleUsersHandler(BaseApiHandler):
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
all_user_ids = self.controller.users.get_all_user_ids()

View File

@ -23,15 +23,23 @@ new_server_schema = {
"type": "string",
"examples": ["My Server"],
"minLength": 2,
"pattern": "^[^/\\\\]*$",
"pattern": r"^[^/\\\\#]*$",
"error": "serverCreateName",
},
"roles": {
"title": "Roles to add",
"type": "array",
"examples": [1, 2, 3],
"error": "typeList",
},
"roles": {"title": "Roles to add", "type": "array", "examples": [1, 2, 3]},
"stop_command": {
"title": "Stop command",
"description": '"" means the default for the server creation type.',
"type": "string",
"default": "",
"examples": ["stop", "end"],
"error": "typeString",
"fill": True,
},
"log_location": {
"title": "Log file",
@ -39,11 +47,15 @@ new_server_schema = {
"type": "string",
"default": "",
"examples": ["./logs/latest.log", "./proxy.log.0"],
"error": "typeString",
"fill": True,
},
"crashdetection": {
"title": "Crash detection",
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
"autostart": {
"title": "Autostart",
@ -51,6 +63,8 @@ new_server_schema = {
+ " automatically when Crafty is launched.",
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
"autostart_delay": {
"title": "Autostart delay",
@ -58,12 +72,16 @@ new_server_schema = {
"type": "number",
"default": 10,
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
"monitoring_type": {
"title": "Server monitoring type",
"type": "string",
"default": "minecraft_java",
"enum": ["minecraft_java", "minecraft_bedrock", "none"],
"error": "enumErr",
"fill": True,
# TODO: SteamCMD, RakNet, etc.
},
"minecraft_java_monitoring_data": {
@ -77,6 +95,8 @@ new_server_schema = {
"default": "127.0.0.1",
"examples": ["127.0.0.1"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"port": {
"title": "Port",
@ -84,6 +104,8 @@ new_server_schema = {
"examples": [25565],
"default": 25565,
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
},
},
@ -98,6 +120,8 @@ new_server_schema = {
"default": "127.0.0.1",
"examples": ["127.0.0.1"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"port": {
"title": "Port",
@ -105,6 +129,8 @@ new_server_schema = {
"examples": [19132],
"default": 19132,
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
},
},
@ -114,6 +140,8 @@ new_server_schema = {
"type": "string",
"default": "minecraft_java",
"enum": ["minecraft_java", "minecraft_bedrock", "custom"],
"error": "enumErr",
"fill": True,
},
"minecraft_java_create_data": {
"title": "Java creation data",
@ -125,10 +153,14 @@ new_server_schema = {
"type": "string",
"default": "download_jar",
"enum": ["download_jar", "import_server", "import_zip"],
"error": "enumErr",
"fill": True,
},
"download_jar_create_data": {
"title": "JAR download data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": [
"type",
"version",
@ -141,6 +173,8 @@ new_server_schema = {
"title": "Jar Category",
"type": "string",
"examples": ["Mc_java_servers", "Mc_java_proxies"],
"error": "enumErr",
"fill": True,
},
"properties": {
"type": {
@ -148,12 +182,16 @@ new_server_schema = {
"type": "string",
"examples": ["Paper"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"version": {
"title": "Server JAR Version",
"type": "string",
"examples": ["1.18.2"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"mem_min": {
"title": "Minimum JVM memory (in GiBs)",
@ -161,6 +199,8 @@ new_server_schema = {
"examples": [1],
"default": 1,
"exclusiveMinimum": 0,
"error": "typeInteger",
"fill": True,
},
"mem_max": {
"title": "Maximum JVM memory (in GiBs)",
@ -168,6 +208,8 @@ new_server_schema = {
"examples": [2],
"default": 2,
"exclusiveMinimum": 0,
"error": "typeInteger",
"fill": True,
},
"server_properties_port": {
"title": "Port",
@ -175,17 +217,23 @@ new_server_schema = {
"examples": [25565],
"default": 25565,
"minimum": 0,
"error": "typeInteger",
"fill": True,
},
"agree_to_eula": {
"title": "Agree to the EULA",
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
},
},
"import_server_create_data": {
"title": "Import server data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": [
"existing_server_path",
"jarfile",
@ -200,6 +248,8 @@ new_server_schema = {
"type": "string",
"examples": ["/var/opt/server"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"jarfile": {
"title": "JAR file",
@ -207,6 +257,8 @@ new_server_schema = {
"type": "string",
"examples": ["paper.jar", "jars/vanilla-1.12.jar"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"mem_min": {
"title": "Minimum JVM memory (in GiBs)",
@ -214,6 +266,8 @@ new_server_schema = {
"examples": [1],
"default": 1,
"exclusiveMinimum": 0,
"error": "typeInteger",
"fill": True,
},
"mem_max": {
"title": "Maximum JVM memory (in GiBs)",
@ -221,6 +275,8 @@ new_server_schema = {
"examples": [2],
"default": 2,
"exclusiveMinimum": 0,
"error": "typeInteger",
"fill": True,
},
"server_properties_port": {
"title": "Port",
@ -228,17 +284,23 @@ new_server_schema = {
"examples": [25565],
"default": 25565,
"minimum": 0,
"error": "typeInteger",
"fill": True,
},
"agree_to_eula": {
"title": "Agree to the EULA",
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
},
},
"import_zip_create_data": {
"title": "Import ZIP server data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": [
"zip_path",
"zip_root",
@ -254,6 +316,8 @@ new_server_schema = {
"type": "string",
"examples": ["/var/opt/server.zip"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"zip_root": {
"title": "Server root directory",
@ -261,6 +325,8 @@ new_server_schema = {
"type": "string",
"examples": ["/", "/paper-server/", "server-1"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"jarfile": {
"title": "JAR file",
@ -268,6 +334,8 @@ new_server_schema = {
"type": "string",
"examples": ["paper.jar", "jars/vanilla-1.12.jar"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"mem_min": {
"title": "Minimum JVM memory (in GiBs)",
@ -275,6 +343,8 @@ new_server_schema = {
"examples": [1],
"default": 1,
"exclusiveMinimum": 0,
"error": "typeInteger",
"fill": True,
},
"mem_max": {
"title": "Maximum JVM memory (in GiBs)",
@ -282,6 +352,8 @@ new_server_schema = {
"examples": [2],
"default": 2,
"exclusiveMinimum": 0,
"error": "typeInteger",
"fill": True,
},
"server_properties_port": {
"title": "Port",
@ -289,11 +361,15 @@ new_server_schema = {
"examples": [25565],
"default": 25565,
"minimum": 0,
"error": "typeInteger",
"fill": True,
},
"agree_to_eula": {
"title": "Agree to the EULA",
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
},
},
@ -342,10 +418,14 @@ new_server_schema = {
"type": "string",
"default": "import_server",
"enum": ["download_exe", "import_server", "import_zip"],
"error": "enumErr",
"fill": True,
},
"download_exe_create_data": {
"title": "Import server data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": [],
"properties": {
"agree_to_eula": {
@ -358,6 +438,8 @@ new_server_schema = {
"import_server_create_data": {
"title": "Import server data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": ["existing_server_path", "executable"],
"properties": {
"existing_server_path": {
@ -366,6 +448,8 @@ new_server_schema = {
"type": "string",
"examples": ["/var/opt/server"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"executable": {
"title": "Executable File",
@ -374,6 +458,8 @@ new_server_schema = {
"type": "string",
"examples": ["bedrock_server.exe"],
"minlength": 1,
"error": "typeString",
"fill": True,
},
"command": {
"title": "Command",
@ -381,12 +467,16 @@ new_server_schema = {
"default": "echo foo bar baz",
"examples": ["LD_LIBRARY_PATH=. ./bedrock_server"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
},
},
"import_zip_create_data": {
"title": "Import ZIP server data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": ["zip_path", "zip_root", "command"],
"properties": {
"zip_path": {
@ -395,6 +485,8 @@ new_server_schema = {
"type": "string",
"examples": ["/var/opt/server.zip"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"executable": {
"title": "Executable File",
@ -403,6 +495,8 @@ new_server_schema = {
"type": "string",
"examples": ["bedrock_server.exe"],
"minlength": 1,
"error": "typeString",
"fill": True,
},
"zip_root": {
"title": "Server root directory",
@ -410,6 +504,8 @@ new_server_schema = {
"type": "string",
"examples": ["/", "/paper-server/", "server-1"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"command": {
"title": "Command",
@ -417,6 +513,8 @@ new_server_schema = {
"default": "echo foo bar baz",
"examples": ["LD_LIBRARY_PATH=. ./bedrock_server"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
},
},
@ -464,6 +562,8 @@ new_server_schema = {
"custom_create_data": {
"title": "Custom creation data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": [
"working_directory",
"executable_update",
@ -476,28 +576,38 @@ new_server_schema = {
"type": "string",
"default": "",
"examples": ["/mnt/mydrive/server-configs/", "./subdirectory", ""],
"error": "typeString",
"fill": True,
},
"executable_update": {
"title": "Executable Updation",
"description": "Also configurable later on and for other servers",
"type": "object",
"error": "enumErr",
"fill": True,
"required": ["enabled", "file", "url"],
"properties": {
"enabled": {
"title": "Enabled",
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
"file": {
"title": "Executable to update",
"type": "string",
"default": "",
"examples": ["./paper.jar"],
"error": "typeString",
"fill": True,
},
"url": {
"title": "URL to download the executable from",
"type": "string",
"default": "",
"error": "typeString",
"fill": True,
},
},
},
@ -506,6 +616,8 @@ new_server_schema = {
"type": "string",
"default": "raw_exec",
"enum": ["raw_exec", "import_server", "import_zip"],
"error": "enumErr",
"fill": True,
},
"raw_exec_create_data": {
"title": "Raw execution command create data",
@ -518,12 +630,16 @@ new_server_schema = {
"default": "echo foo bar baz",
"examples": ["caddy start"],
"minLength": 1,
"error": "typeString",
"fill": True,
}
},
},
"import_server_create_data": {
"title": "Import server data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": ["existing_server_path", "command"],
"properties": {
"existing_server_path": {
@ -532,6 +648,8 @@ new_server_schema = {
"type": "string",
"examples": ["/var/opt/server"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"command": {
"title": "Command",
@ -539,12 +657,16 @@ new_server_schema = {
"default": "echo foo bar baz",
"examples": ["caddy start"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
},
},
"import_zip_create_data": {
"title": "Import ZIP server data",
"type": "object",
"error": "enumErr",
"fill": True,
"required": ["zip_path", "zip_root", "command"],
"properties": {
"zip_path": {
@ -553,6 +675,8 @@ new_server_schema = {
"type": "string",
"examples": ["/var/opt/server.zip"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"zip_root": {
"title": "Server root directory",
@ -560,6 +684,8 @@ new_server_schema = {
"type": "string",
"examples": ["/", "/paper-server/", "server-1"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
"command": {
"title": "Command",
@ -567,6 +693,8 @@ new_server_schema = {
"default": "echo foo bar baz",
"examples": ["caddy start"],
"minLength": 1,
"error": "typeString",
"fill": True,
},
},
},
@ -690,7 +818,16 @@ class ApiServersIndexHandler(BaseApiHandler):
) = auth_data
if EnumPermissionsCrafty.SERVER_CREATION not in exec_user_crafty_permissions:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = orjson.loads(self.request.body)
@ -700,13 +837,21 @@ class ApiServersIndexHandler(BaseApiHandler):
)
try:
validate(data, new_server_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
# Check to make sure port is allowable
@ -722,7 +867,12 @@ class ApiServersIndexHandler(BaseApiHandler):
port = 19132
if port > 65535 or port < 1:
self.finish_json(
405, {"status": "error", "error": "DATA CONSTRAINT FAILED"}
405,
{
"status": "error",
"error": "DATA CONSTRAINT FAILED",
"error_data": "1 - 65535",
},
)
return
try:

View File

@ -18,7 +18,16 @@ class ApiServersServerActionHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -28,7 +37,16 @@ class ApiServersServerActionHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.COMMANDS not in server_permissions:
# if the user doesn't have Commands permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if action == "clone_server":
if (
@ -49,7 +67,12 @@ class ApiServersServerActionHandler(BaseApiHandler):
self._clone_server(server_id, auth_data[4]["user_id"])
return self.finish_json(200, {"status": "ok"})
return self.finish_json(
200, {"status": "error", "error": "SERVER_LIMIT_REACHED"}
200,
{
"status": "error",
"error": "SERVER_LIMIT_REACHED",
"error_data": "LIMIT REACHED",
},
)
if action == "eula":
return self._agree_eula(server_id, auth_data[4]["user_id"])

View File

@ -14,7 +14,12 @@ logger = logging.getLogger(__name__)
BACKUP_SCHEMA = {
"type": "object",
"properties": {
"filename": {"type": "string", "minLength": 5},
"filename": {
"type": "string",
"minLength": 5,
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -22,14 +27,47 @@ BACKUP_SCHEMA = {
BACKUP_PATCH_SCHEMA = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"backup_location": {"type": "string", "minLength": 1},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"excluded_dirs": {"type": "array"},
"backup_name": {
"type": "string",
"minLength": 3,
"error": "backupName",
},
"backup_location": {
"type": "string",
"minLength": 1,
"error": "typeString",
"fill": True,
},
"max_backups": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"compress": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"shutdown": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"before": {
"type": "string",
"error": "typeString",
"fill": True,
},
"after": {
"type": "string",
"error": "typeString",
"fill": True,
},
"excluded_dirs": {
"type": "array",
"error": "typeList",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -38,13 +76,37 @@ BACKUP_PATCH_SCHEMA = {
BASIC_BACKUP_PATCH_SCHEMA = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"excluded_dirs": {"type": "array"},
"backup_name": {"type": "string", "minLength": 3, "error": "backupName"},
"max_backups": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"compress": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"shutdown": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"before": {
"type": "string",
"error": "typeString",
"fill": True,
},
"after": {
"type": "string",
"error": "typeString",
"fill": True,
},
"excluded_dirs": {
"type": "array",
"error": "typeList",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -179,13 +241,21 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
)
try:
validate(data, BACKUP_SCHEMA)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
@ -202,7 +272,8 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
temp_dir = Helpers.unzip_backup_archive(backup_location, zip_name)
except (FileNotFoundError, NotADirectoryError) as e:
return self.finish_json(
400, {"status": "error", "error": f"NO BACKUP FOUND {e}"}
400,
{"status": "error", "error": "NO BACKUP FOUND", "error_data": e},
)
if server_data["type"] == "minecraft-java":
new_server = self.controller.restore_java_zip_server(
@ -314,13 +385,21 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
validate(data, BACKUP_PATCH_SCHEMA)
else:
validate(data, BASIC_BACKUP_PATCH_SCHEMA)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
backup_conf = self.controller.management.get_backup_config(backup_id)
@ -405,13 +484,21 @@ class ApiServersServerBackupsBackupFilesIndexHandler(BaseApiHandler):
)
try:
validate(data, BACKUP_SCHEMA)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
self.helper.validate_traversal(
@ -432,7 +519,7 @@ class ApiServersServerBackupsBackupFilesIndexHandler(BaseApiHandler):
)
except Exception as e:
return self.finish_json(
400, {"status": "error", "error": f"DELETE FAILED with error {e}"}
400, {"status": "error", "error": "DELETE FAILED", "error_data": e}
)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],

View File

@ -11,14 +11,43 @@ logger = logging.getLogger(__name__)
backup_patch_schema = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"backup_location": {"type": "string", "minLength": 1},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"excluded_dirs": {"type": "array"},
"backup_name": {"type": "string", "minLength": 3, "error": "backupName"},
"backup_location": {
"type": "string",
"minLength": 1,
"error": "typeString",
"fill": True,
},
"max_backups": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"compress": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"shutdown": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"before": {
"type": "string",
"error": "typeString",
"fill": True,
},
"after": {
"type": "string",
"error": "typeString",
"fill": True,
},
"excluded_dirs": {
"type": "array",
"error": "typeList",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -27,13 +56,37 @@ backup_patch_schema = {
basic_backup_patch_schema = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"excluded_dirs": {"type": "array"},
"backup_name": {"type": "string", "minLength": 3, "error": "backupName"},
"max_backups": {
"type": "integer",
"error": "typeInt",
"fill": True,
},
"compress": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"shutdown": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"before": {
"type": "string",
"error": "typeString",
"fill": True,
},
"after": {
"type": "string",
"error": "typeString",
"fill": True,
},
"excluded_dirs": {
"type": "array",
"error": "typeList",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -54,7 +107,16 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200, self.controller.management.get_backups_by_server(server_id)
)
@ -76,18 +138,35 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
validate(data, backup_patch_schema)
else:
validate(data, basic_backup_patch_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -97,7 +176,16 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
# Set the backup location automatically for non-super users. We should probably
# make the default location configurable for SU eventually
if not auth_data[4]["superuser"]:

View File

@ -14,8 +14,17 @@ logger = logging.getLogger(__name__)
files_get_schema = {
"type": "object",
"properties": {
"page": {"type": "string", "minLength": 1},
"path": {"type": "string"},
"page": {
"type": "string",
"minLength": 1,
"error": "typeString",
"fill": True,
},
"path": {
"type": "string",
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -24,8 +33,16 @@ files_get_schema = {
files_patch_schema = {
"type": "object",
"properties": {
"path": {"type": "string"},
"contents": {"type": "string"},
"path": {
"type": "string",
"error": "typeString",
"fill": True,
},
"contents": {
"type": "string",
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -34,7 +51,11 @@ files_patch_schema = {
files_unzip_schema = {
"type": "object",
"properties": {
"folder": {"type": "string"},
"folder": {
"type": "string",
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -43,9 +64,21 @@ files_unzip_schema = {
files_create_schema = {
"type": "object",
"properties": {
"parent": {"type": "string"},
"name": {"type": "string"},
"directory": {"type": "boolean"},
"parent": {
"type": "string",
"error": "typeString",
"fill": True,
},
"name": {
"type": "string",
"error": "typeString",
"fill": True,
},
"directory": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -54,8 +87,16 @@ files_create_schema = {
files_rename_schema = {
"type": "object",
"properties": {
"path": {"type": "string"},
"new_name": {"type": "string"},
"path": {
"type": "string",
"error": "typeString",
"fill": True,
},
"new_name": {
"type": "string",
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -64,7 +105,12 @@ files_rename_schema = {
file_delete_schema = {
"type": "object",
"properties": {
"filename": {"type": "string", "minLength": 5},
"filename": {
"type": "string",
"minLength": 5,
"error": "typeString",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -79,7 +125,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -92,7 +147,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
and EnumPermissionsServer.BACKUP not in server_permissions
):
# if the user doesn't have Files or Backup permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
@ -210,7 +274,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -220,7 +293,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
@ -229,13 +311,21 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
)
try:
validate(data, file_delete_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if not Helpers.validate_traversal(
@ -259,7 +349,9 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
# but not a true boolean value
if proc == True: # pylint: disable=singleton-comparison
return self.finish_json(200, {"status": "ok"})
return self.finish_json(500, {"status": "error", "error": str(proc)})
return self.finish_json(
500, {"status": "error", "error": "SERVER RUNNING", "error_data": str(proc)}
)
def patch(self, server_id: str, _backup_id):
auth_data = self.authenticate_user()
@ -268,7 +360,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -278,7 +379,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
@ -287,13 +397,21 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
)
try:
validate(data, files_patch_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if not Helpers.validate_traversal(
@ -322,7 +440,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -332,7 +459,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
@ -341,13 +477,21 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
)
try:
validate(data, files_create_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
path = os.path.join(data["parent"], data["name"])
@ -389,7 +533,16 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -399,7 +552,16 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
@ -408,13 +570,21 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
)
try:
validate(data, files_rename_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
path = data["path"]
@ -455,7 +625,16 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -465,7 +644,16 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
@ -474,13 +662,21 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
)
try:
validate(data, files_create_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
path = os.path.join(data["parent"], data["name"])
@ -522,7 +718,16 @@ class ApiServersServerFilesZipHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -532,7 +737,16 @@ class ApiServersServerFilesZipHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
@ -541,13 +755,21 @@ class ApiServersServerFilesZipHandler(BaseApiHandler):
)
try:
validate(data, files_unzip_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
folder = data["folder"]

View File

@ -14,7 +14,16 @@ class ApiServersServerHistoryHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
srv = ServersController().get_server_instance_by_id(server_id)
history = srv.get_server_history()

View File

@ -12,24 +12,101 @@ logger = logging.getLogger(__name__)
server_patch_schema = {
"type": "object",
"properties": {
"server_name": {"type": "string", "minLength": 2, "pattern": "^[^/\\\\]*$"},
"backup_path": {"type": "string"},
"executable": {"type": "string"},
"log_path": {"type": "string", "minLength": 1},
"execution_command": {"type": "string", "minLength": 1},
"java_selection": {"type": "string"},
"auto_start": {"type": "boolean"},
"auto_start_delay": {"type": "integer", "minimum": 0},
"crash_detection": {"type": "boolean"},
"stop_command": {"type": "string"},
"executable_update_url": {"type": "string"},
"server_ip": {"type": "string", "minLength": 1},
"server_port": {"type": "integer"},
"shutdown_timeout": {"type": "integer", "minimum": 0},
"logs_delete_after": {"type": "integer", "minimum": 0},
"ignored_exits": {"type": "string"},
"show_status": {"type": "boolean"},
"count_players": {"type": "boolean"},
"server_name": {
"type": "string",
"minLength": 2,
"pattern": r"^[^/\\\\#]*$",
"error": "serverCreateName",
},
"backup_path": {
"type": "string",
"error": "typeString",
"fill": True,
},
"executable": {
"type": "string",
"error": "typeString",
"fill": True,
},
"log_path": {
"type": "string",
"minLength": 1,
"error": "serverLogPath",
},
"execution_command": {
"type": "string",
"minLength": 1,
"error": "serverExeCommand",
},
"java_selection": {
"type": "string",
"error": "typeString",
"fill": True,
},
"auto_start": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"auto_start_delay": {
"type": "integer",
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
"crash_detection": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"stop_command": {
"type": "string",
"error": "typeString",
"fill": True,
},
"executable_update_url": {
"type": "string",
"error": "typeString",
"fill": True,
},
"server_ip": {
"type": "string",
"minLength": 1,
"error": "typeString",
"fill": True,
},
"server_port": {
"type": "integer",
"error": "typeInt",
"fill": True,
},
"shutdown_timeout": {
"type": "integer",
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
"logs_delete_after": {
"type": "integer",
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
"ignored_exits": {
"type": "string",
"error": "typeString",
"fill": True,
},
"show_status": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"count_players": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -37,17 +114,64 @@ server_patch_schema = {
basic_server_patch_schema = {
"type": "object",
"properties": {
"server_name": {"type": "string", "minLength": 1},
"executable": {"type": "string"},
"java_selection": {"type": "string"},
"auto_start": {"type": "boolean"},
"auto_start_delay": {"type": "integer", "minimum": 0},
"crash_detection": {"type": "boolean"},
"stop_command": {"type": "string"},
"shutdown_timeout": {"type": "integer"},
"logs_delete_after": {"type": "integer", "minimum": 0},
"ignored_exits": {"type": "string"},
"count_players": {"type": "boolean"},
"server_name": {
"type": "string",
"minLength": 1,
"error": "serverCreateName",
"fill": True,
},
"executable": {
"type": "string",
"error": "typeString",
"fill": True,
},
"java_selection": {
"type": "string",
"error": "typeString",
"fill": True,
},
"auto_start": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"auto_start_delay": {
"type": "integer",
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
"crash_detection": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
"stop_command": {
"type": "string",
"error": "typeString",
"fill": True,
},
"shutdown_timeout": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"logs_delete_after": {
"type": "integer",
"minimum": 0,
"error": "typeIntMinVal0",
"fill": True,
},
"ignored_exits": {
"type": "string",
"error": "typeString",
"fill": True,
},
"count_players": {
"type": "boolean",
"error": "typeBool",
"fill": True,
},
},
"additionalProperties": False,
"minProperties": 1,
@ -62,7 +186,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
server_obj = self.controller.servers.get_server_obj(server_id)
server = model_to_dict(server_obj)
@ -89,19 +222,36 @@ class ApiServersServerIndexHandler(BaseApiHandler):
validate(data, server_patch_schema)
else:
validate(data, basic_server_patch_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -111,7 +261,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
server_obj = self.controller.servers.get_server_obj(server_id)
java_flag = False
@ -125,7 +284,12 @@ class ApiServersServerIndexHandler(BaseApiHandler):
setattr(server_obj, "execution_command", command)
except ValueError:
return self.finish_json(
400, {"status": "error", "error": "INVALID EXECUTION COMMAND"}
400,
{
"status": "error",
"error": "INVALID EXECUTION COMMAND",
"error_data": "INVALID COMMAND",
},
)
java_flag = True
@ -154,7 +318,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -164,7 +337,16 @@ class ApiServersServerIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
logger.info(
(

View File

@ -29,7 +29,16 @@ class ApiServersServerLogsHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -39,7 +48,16 @@ class ApiServersServerLogsHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.LOGS not in server_permissions:
# if the user doesn't have Logs permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
server_data = self.controller.servers.get_server_data_by_id(server_id)

View File

@ -14,7 +14,16 @@ class ApiServersServerStatsHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
srv = ServersController().get_server_instance_by_id(server_id)
latest = srv.stats_helper.get_latest_server_stats()

View File

@ -15,7 +15,16 @@ class ApiServersServerStdinHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -25,7 +34,16 @@ class ApiServersServerStdinHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.COMMANDS not in server_permissions:
# if the user doesn't have Commands permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
svr = self.controller.servers.get_server_obj_optional(server_id)
if svr is None:
@ -35,7 +53,16 @@ class ApiServersServerStdinHandler(BaseApiHandler):
"Crafty can't access the server object. "
"Please report this to the devs"
)
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
decoded = self.request.body.decode("utf-8")
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
@ -50,5 +77,9 @@ class ApiServersServerStdinHandler(BaseApiHandler):
)
self.finish_json(
200,
{"status": "error", "error": "SERVER_NOT_RUNNING"},
{
"status": "error",
"error": "SERVER_NOT_RUNNING",
"error_data": "SERVER NOT RUNNING",
},
)

View File

@ -13,18 +13,32 @@ logger = logging.getLogger(__name__)
new_task_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"name": {
"type": "string",
"error": "typeString",
"fill": True,
},
"enabled": {
"type": "boolean",
"default": True,
"error": "typeBool",
"fill": True,
},
"action": {
"type": "string",
"error": "typeString",
"fill": True,
},
"action_id": {
"type": "string",
"error": "typeString",
"fill": True,
},
"interval": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"interval": {"type": "integer"},
"interval_type": {
"type": "string",
"enum": [
@ -37,13 +51,43 @@ new_task_schema = {
# CRON tasks:
"",
],
"error": "enumErr",
"fill": True,
},
"start_time": {
"type": "string",
"pattern": r"\d{1,2}:\d{1,2}",
"error": "typeString",
"fill": True,
},
"command": {
"type": ["string", "null"],
"error": "typeString",
"fill": True,
},
"one_time": {
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
"cron_string": {
"type": "string",
"default": "",
"error": "typeString",
"fill": True,
},
"parent": {
"type": ["integer", "null"],
"error": "typeInteger",
"fill": True,
},
"delay": {
"type": "integer",
"default": 0,
"error": "typeInteger",
"fill": True,
},
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
"command": {"type": ["string", "null"]},
"one_time": {"type": "boolean", "default": False},
"cron_string": {"type": "string", "default": ""},
"parent": {"type": ["integer", "null"]},
"delay": {"type": "integer", "default": 0},
},
"additionalProperties": False,
"minProperties": 1,
@ -68,19 +112,36 @@ class ApiServersServerTasksIndexHandler(BaseApiHandler):
try:
validate(data, new_task_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -90,7 +151,16 @@ class ApiServersServerTasksIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
data["server_id"] = server_id
if not data.get("start_time"):
data["start_time"] = "00:00"

View File

@ -18,14 +18,24 @@ task_patch_schema = {
"enabled": {
"type": "boolean",
"default": True,
"error": "typeBool",
"fill": True,
},
"action": {
"type": "string",
"error": "typeString",
"fill": True,
},
"action_id": {
"type": "string",
"error": "typeString",
"fill": True,
},
"interval": {
"type": "integer",
"error": "typeInteger",
"fill": True,
},
"interval": {"type": "integer"},
"interval_type": {
"type": "string",
"enum": [
@ -38,14 +48,48 @@ task_patch_schema = {
# CRON tasks:
"",
],
"error": "enumErr",
"fill": True,
},
"name": {
"type": "string",
"error": "typeString",
"fill": True,
},
"start_time": {
"type": "string",
"pattern": r"\d{1,2}:\d{1,2}",
"error": "typeString",
"fill": True,
},
"command": {
"type": ["string", "null"],
"error": "typeString",
"fill": True,
},
"one_time": {
"type": "boolean",
"default": False,
"error": "typeBool",
"fill": True,
},
"cron_string": {
"type": "string",
"default": "",
"error": "typeString",
"fill": True,
},
"parent": {
"type": ["integer", "null"],
"error": "typeInteger",
"fill": True,
},
"delay": {
"type": "integer",
"default": 0,
"error": "typeInteger",
"fill": True,
},
"name": {"type": "string"},
"start_time": {"type": "string", "pattern": r"\d{1,2}:\d{1,2}"},
"command": {"type": ["string", "null"]},
"one_time": {"type": "boolean", "default": False},
"cron_string": {"type": "string", "default": ""},
"parent": {"type": ["integer", "null"]},
"delay": {"type": "integer", "default": 0},
},
"additionalProperties": False,
"minProperties": 1,
@ -66,7 +110,16 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(200, self.controller.management.get_scheduled_task(task_id))
def delete(self, server_id: str, task_id: str):
@ -82,13 +135,23 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
self.tasks_manager.remove_job(task_id)
except Exception:
except Exception as why:
return self.finish_json(
400, {"status": "error", "error": "NO SCHEDULE FOUND"}
400,
{"status": "error", "error": "NO SCHEDULE FOUND", "error_data": why},
)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
@ -114,19 +177,36 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
try:
validate(data, task_patch_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -136,7 +216,16 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
# Checks to make sure some doofus didn't actually make the newly
# created task a child of itself.

View File

@ -14,13 +14,40 @@ class ApiServersServerUsersHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if EnumPermissionsCrafty.USER_CONFIG not in auth_data[1]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if EnumPermissionsCrafty.ROLES_CONFIG not in auth_data[1]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200,

View File

@ -16,16 +16,45 @@ new_webhook_schema = {
"webhook_type": {
"type": "string",
"enum": WebhookFactory.get_supported_providers(),
"error": "typeString",
"fill": True,
},
"name": {
"type": "string",
"error": "typeString",
"fill": True,
},
"url": {
"type": "string",
"error": "typeString",
"fill": True,
},
"bot_name": {
"type": "string",
"error": "typeString",
"fill": True,
},
"trigger": {
"type": "array",
"error": "typeString",
"fill": True,
},
"body": {
"type": "string",
"error": "typeString",
"fill": True,
},
"color": {
"type": "string",
"default": "#005cd1",
"error": "typeString",
"fill": True,
},
"name": {"type": "string"},
"url": {"type": "string"},
"bot_name": {"type": "string"},
"trigger": {"type": "array"},
"body": {"type": "string"},
"color": {"type": "string", "default": "#005cd1"},
"enabled": {
"type": "boolean",
"default": True,
"error": "typeBool",
"fill": True,
},
},
"additionalProperties": False,
@ -47,7 +76,16 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.finish_json(
200,
{
@ -70,19 +108,36 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
try:
validate(data, new_webhook_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -92,7 +147,16 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
data["server_id"] = server_id
self.controller.management.add_to_audit_log(

View File

@ -17,16 +17,45 @@ webhook_patch_schema = {
"webhook_type": {
"type": "string",
"enum": WebhookFactory.get_supported_providers(),
"error": "typeString",
"fill": True,
},
"name": {
"type": "string",
"error": "typeString",
"fill": True,
},
"url": {
"type": "string",
"error": "typeString",
"fill": True,
},
"bot_name": {
"type": "string",
"error": "typeString",
"fill": True,
},
"trigger": {
"type": "array",
"error": "typeString",
"fill": True,
},
"body": {
"type": "string",
"error": "typeString",
"fill": True,
},
"color": {
"type": "string",
"default": "#005cd1",
"error": "typeString",
"fill": True,
},
"name": {"type": "string"},
"url": {"type": "string"},
"bot_name": {"type": "string"},
"trigger": {"type": "array"},
"body": {"type": "string"},
"color": {"type": "string", "default": "#005cd1"},
"enabled": {
"type": "boolean",
"default": True,
"error": "typeBool",
"fill": True,
},
},
"additionalProperties": False,
@ -48,13 +77,27 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if (
not str(webhook_id)
in self.controller.management.get_webhooks_by_server(server_id).keys()
):
return self.finish_json(
400, {"status": "error", "error": "NO WEBHOOK FOUND"}
400,
{
"status": "error",
"error": "NO WEBHOOK FOUND",
"error_data": "NOT FOUND",
},
)
self.finish_json(
200,
@ -77,13 +120,27 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
self.controller.management.delete_webhook(webhook_id)
except Exception:
return self.finish_json(
400, {"status": "error", "error": "NO WEBHOOK FOUND"}
400,
{
"status": "error",
"error": "NO WEBHOOK FOUND",
"error_data": "NOT FOUND",
},
)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
@ -108,19 +165,36 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
try:
validate(data, webhook_patch_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -130,7 +204,16 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
data["server_id"] = server_id
if "trigger" in data.keys():
@ -163,7 +246,16 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
@ -173,7 +265,16 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
webhook = self.controller.management.get_webhook_by_id(webhook_id)
try:
webhook_provider = WebhookFactory.create_provider(webhook["webhook_type"])
@ -188,6 +289,8 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
bot_name="Crafty Webhooks Tester",
)
except Exception as e:
self.finish_json(500, {"status": "error", "error": str(e)})
self.finish_json(
500, {"status": "error", "error": "WEBHOOK ERROR", "error_data": str(e)}
)
self.finish_json(200, {"status": "ok"})

View File

@ -77,7 +77,16 @@ class ApiUsersIndexHandler(BaseApiHandler):
) = auth_data
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
try:
data = json.loads(self.request.body)
@ -88,12 +97,15 @@ class ApiUsersIndexHandler(BaseApiHandler):
try:
validate(data, new_user_schema)
except ValidationError as e:
err = self.translator.translate(
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
e.schema["error"],
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
@ -125,11 +137,23 @@ class ApiUsersIndexHandler(BaseApiHandler):
if username.lower() in ["system", ""]:
return self.finish_json(
400, {"status": "error", "error": "INVALID_USERNAME"}
400,
{
"status": "error",
"error": "INVALID_USERNAME",
"error_data": "INVALID USERNAME",
},
)
if self.controller.users.get_id_by_name(username) is not None:
return self.finish_json(400, {"status": "error", "error": "USER_EXISTS"})
return self.finish_json(
400,
{
"status": "error",
"error": "USER_EXISTS",
"error_data": "UNIQUE VALUE ERROR",
},
)
if roles is None:
roles = set()
@ -155,7 +179,14 @@ class ApiUsersIndexHandler(BaseApiHandler):
if new_superuser and not superuser:
return self.finish_json(
400, {"status": "error", "error": "INVALID_SUPERUSER_CREATE"}
400,
{
"status": "error",
"error": "INVALID_SUPERUSER_CREATE",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
for role in roles:
@ -166,7 +197,14 @@ class ApiUsersIndexHandler(BaseApiHandler):
and not superuser
):
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_CREATE"}
400,
{
"status": "error",
"error": "INVALID_ROLES_CREATE",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
# TODO: do this in the most efficient way

View File

@ -134,13 +134,21 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
)
try:
validate(data, user_patch_schema)
except ValidationError as e:
except ValidationError as why:
offending_key = ""
if why.schema.get("fill", None):
offending_key = why.path[0] if why.path else None
err = f"""{offending_key} {self.translator.translate(
"validators",
why.schema.get("error"),
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)} {why.schema.get("enum", "")}"""
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
"error_data": f"{str(err)}",
},
)
if user_id == "@me":
@ -161,7 +169,12 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
if "username" in data:
if data["username"].lower() in ["system", ""]:
return self.finish_json(
400, {"status": "error", "error": "INVALID_USERNAME"}
400,
{
"status": "error",
"error": "INVALID_USERNAME",
"error_data": "INVALID USERNAME",
},
)
if self.controller.users.get_id_by_name(
data["username"]
@ -171,7 +184,12 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
user_id
):
return self.finish_json(
400, {"status": "error", "error": "USER_EXISTS"}
400,
{
"status": "error",
"error": "USER_EXISTS",
"error_data": "UNIQUE CONSTAINT FAILED",
},
)
if "superuser" in data:
@ -179,7 +197,14 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
# Checks if user is trying to change super user status
# of self without superuser. We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_SUPERUSER_MODIFY"}
400,
{
"status": "error",
"error": "INVALID_SUPERUSER_MODIFY",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if not superuser:
# The user is not superuser so they can't change the superuser status
@ -190,13 +215,27 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
# Checks if user is trying to change permissions
# of self without superuser. We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_PERMISSIONS_MODIFY"}
400,
{
"status": "error",
"error": "INVALID_PERMISSIONS_MODIFY",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
# Checks if user is trying to change permissions of someone
# else without User Config permission. We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_PERMISSIONS_MODIFY"}
400,
{
"status": "error",
"error": "INVALID_PERMISSIONS_MODIFY",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if "roles" in data:
@ -204,13 +243,27 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
# Checks if user is trying to change roles of
# self without superuser. We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
400,
{
"status": "error",
"error": "INVALID_ROLES_MODIFY",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
# Checks if user is trying to change roles of someone
# else without User Config permission. We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
400,
{
"status": "error",
"error": "INVALID_ROLES_MODIFY",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
user_modify = self.controller.users.get_user_roles_id(user_id)
@ -229,7 +282,14 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
for item in user_modify:
print(type(role), type(item))
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
400,
{
"status": "error",
"error": "INVALID_ROLES_MODIFY",
"error_data": self.helper.translation.translate(
"error", "no-file", auth_data[4]["lang"]
),
},
)
user_obj = HelperUsers.get_user_model(user_id)
@ -237,7 +297,14 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
if str(user["user_id"]) != str(user_obj.manager) and not user["superuser"]:
# TODO: edit your own password
return self.finish_json(
400, {"status": "error", "error": "INVALID_PASSWORD_MODIFY"}
400,
{
"status": "error",
"error": "INVALID_PASSWORD_MODIFY",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
if "roles" in data:

View File

@ -13,7 +13,16 @@ class ApiOpenMetricsCraftyHandler(BaseMetricsHandler):
if not auth_data[3]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.get_registry()

View File

@ -14,13 +14,29 @@ class ApiOpenMetricsServersHandler(BaseMetricsHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": self.helper.translation.translate(
"validators", "insufficientPerms", auth_data[4]["lang"]
),
},
)
self.get_registry(server_id)
def get_registry(self, server_id=None) -> None:
if server_id is None:
return self.finish_json(500, {"status": "error", "error": "UNKNOWN_SERVER"})
return self.finish_json(
500,
{
"status": "error",
"error": "UNKNOWN_SERVER",
"error_data": "UNKNOWN SERVER",
},
)
# Prepare parameters
registry = (

View File

@ -130,6 +130,7 @@ class ServerHandler(BaseHandler):
else None
),
"superuser": superuser,
"themes": self.helper.get_themes(),
}
if superuser:

View File

@ -12,6 +12,7 @@ class StatusHandler(BaseHandler):
"lang": self.helper.get_setting("language"),
"lang_page": self.helper.get_lang_page(self.helper.get_setting("language")),
"servers": self.controller.servers.get_all_servers_stats(),
"themes": self.helper.get_themes(),
}
running = 0
for srv in page_data["servers"]:

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,8 @@
height: 1.5rem;
width: 3rem;
border-radius: 1.5rem;
color: #6b7381;
background: #bdc1c8;
color: var(--gray);
background: var(--select-bg);
}
.btn-toggle:focus,
@ -52,7 +52,7 @@
width: 1.125rem;
height: 1.125rem;
border-radius: 1.125rem;
background: #fff;
background: var(--white);
transition: left 0.25s;
}
@ -76,7 +76,7 @@
.btn-toggle.btn-sm:before,
.btn-toggle.btn-sm:after {
line-height: -0.5rem;
color: #fff;
color: var(--white);
letter-spacing: 0.75px;
left: 0.4125rem;
width: 2.325rem;
@ -106,11 +106,11 @@
.btn-toggle:before,
.btn-toggle:after {
color: #6b7381;
color: var(--gray);
}
.btn-toggle.active {
background-color: #29b5a8;
background-color: var(--success);
}
.btn-toggle.btn-lg {
@ -162,7 +162,7 @@
width: 1.875rem;
height: 1.875rem;
border-radius: 1.875rem;
background: #fff;
background: var(--white);
transition: left 0.25s;
}
@ -186,7 +186,7 @@
.btn-toggle.btn-lg.btn-sm:before,
.btn-toggle.btn-lg.btn-sm:after {
line-height: 0.5rem;
color: #fff;
color: var(--white);
letter-spacing: 0.75px;
left: 0.6875rem;
width: 3.875rem;
@ -423,7 +423,7 @@
.btn-toggle.btn-info:before,
.btn-toggle.btn-info:after {
color: #6b7381;
color: var(--gray);
}
.btn-toggle.btn-info.active {
@ -431,17 +431,17 @@
}
.btn-toggle.btn-secondary {
color: #6b7381;
background: #bdc1c8;
color: var(--gray);
background: var(--select-bg);
}
.btn-toggle.btn-secondary:before,
.btn-toggle.btn-secondary:after {
color: #6b7381;
color: var(--gray);
}
.btn-toggle.btn-secondary.active {
background-color: #ff8300;
background-color: var(--warning);
}
/**************************************************************/

View File

@ -1,3 +1,97 @@
/**************************************************************/
/* CSS From base.html */
/**************************************************************/
body:not(.sidebar-icon-only) .navbar-toggler .mdi-chevron-double-right {
display: none;
}
body.sidebar-icon-only .navbar-toggler .mdi-chevron-double-left {
display: none;
}
/**************************************************************/
/**************************************************************/
/* CSS as bootstrap extensions */
/**************************************************************/
.overflow-x-auto {
overflow-x: auto;
}
.overflow-y-auto {
overflow-y: auto;
}
.overflow-scroll {
overflow: scroll;
}
.text-none {
text-transform: none;
}
.vw-40 {
width: 40vw !important;
}
.vh-40 {
height: 40vh !important;
}
.mw-25 {
max-width: 25% !important;
}
.mw-50 {
max-width: 50% !important;
}
.mw-75 {
max-width: 75% !important;
}
.mw-80 {
max-width: 80% !important;
}
.mh-25 {
max-height: 25% !important;
}
.mh-50 {
max-height: 50% !important;
}
.mh-75 {
max-height: 75% !important;
}
.mh-80 {
max-height: 80% !important;
}
/**************************************************************/
/**************************************************************/
/* CSS for MainMenu in partials/_sidebar.html */
/**************************************************************/
@media screen and (max-width: 991px) {
.sidebar-offcanvas {
-webkit-transition: all 0.25s cubic-bezier(.22, .61, .36, 1);
transition: all 0.25s cubic-bezier(.22, .61, .36, 1);
box-shadow: 0px 8px 17px 2px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12), 0px 5px 5px -3px rgba(0, 0, 0, 0.2);
}
}
li.nav-item.nav-category {
margin-top: 10px;
}
/**************************************************************/
/**************************************************************/
/* CSS For ? */
/**************************************************************/
.select-css option {
background-color: var(--deep-bg);
color: var(--base-text)
@ -93,11 +187,11 @@ td::-webkit-scrollbar {
}
.mc-log-error {
color: #af463f;
color: var(--danger);
}
.mc-log-fatal {
color: #da0f00;
color: var(--red);
}
.mc-log-keyword {
@ -118,16 +212,6 @@ body {
/* Firefox */
}
/* Webkit */
/* Didn't really work out
::-webkit-scrollbar { background-color: #202538; }
::-webkit-scrollbar-button { background-color: #1D212F; }
::-webkit-scrollbar-thumb { background-color: #777A86; }
::-webkit-scrollbar-thumb:hover { background-color: #646773; }
::-webkit-scrollbar-track { background-color: #202538; }
::-webkit-scrollbar-corner { background-color: #202538; }*/
.actions_serverlist>a>i {
cursor: pointer;
}
@ -151,7 +235,52 @@ body {
}
/**************************************************************/
/* CSS for Froms Displays */
/**************************************************************/
/* CSS for Dashboard */
/**************************************************************/
#desc_id {
-ms-overflow-style: none;
/* for Internet Explorer, Edge */
scrollbar-width: none;
/* for Firefox */
overflow-y: scroll;
}
#desc_id::-webkit-scrollbar {
display: none;
/* for Chrome, Safari, and Opera */
}
.gray {
color: var(--gray) !important;
}
.disk-usage {
height: 20px;
width: 100%;
background-color: rgb(139, 139, 139) !important
}
.disk-usage>.progress-bar {
color: black;
height: 100%;
}
.server-desc {
overflow-wrap: break-word !important;
max-width: 85px !important;
overflow: scroll;
}
.server-alert {
color: var(--red) !important;
}
/**************************************************************/
/**************************************************************/
/* CSS for Forms Displays */
/**************************************************************/
div>.input-group>.custom-file-input {
position: relative !important;
@ -196,6 +325,7 @@ div>.input-group>.form-control {
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
overflow: scroll;
}
.no-scroll::-webkit-scrollbar {
@ -219,7 +349,11 @@ div>.input-group>.form-control {
}
.custom-switch .custom-control-label::after {
top: calc(-0.125rem + 1px);
top: calc(-0.175rem);
}
.custom-radio .custom-control-label::after {
top: calc(-0.175rem);
}
a.btn-primary {
@ -235,7 +369,11 @@ button.btn-primary {
/**************************************************************/
/* CSS for Tables Displays */
/**************************************************************/
td>ul {
table.table {
overflow: scroll;
}
div td>ul {
margin: auto;
}
@ -268,6 +406,12 @@ div.warnings div.wssError a:hover {
color: var(--white-smoke);
}
noscript.noscript-warning {
padding: 20px;
background-color: rgb(247, 151, 15);
/* TODO change background color */
}
/**************************************************************/
/**************************************************************/
@ -278,6 +422,99 @@ div.warnings div.wssError a:hover {
}
/**************************************************************/
/**************************************************************/
/* CSS for Annoucements Displays */
/**************************************************************/
.annoucement-item>p {
float: right;
}
.annoucement-title {
color: var(--info);
}
.annoucement-item:hover>a .annoucement-title {
color: var(--purple);
}
div.support_progress {
height: 15px;
width: 100%;
}
/**************************************************************/
/**************************************************************/
/* CSS for API Keys Displays */
/**************************************************************/
.api-key {
white-space: pre-wrap;
color: white;
word-break: break-all;
background: grey;
border-radius: 5px;
}
/**************************************************************/
/**************************************************************/
/* CSS for Virtual Console Displays */
/**************************************************************/
.virt_console {
width: 100%;
font-size: .8em;
padding: 5px 10px;
border: 1px solid var(--outline);
background-color: var(--card-banner-bg);
height: 500px;
overflow: scroll;
}
/**************************************************************/
/**************************************************************/
/* CSS for Lists Displays */
/**************************************************************/
ul>li.list-group-item {
background-color: var(--card-banner-bg);
}
ul>li.list-group-item i {
margin: 0;
}
/**************************************************************/
/**************************************************************/
/* CSS for Card Displays */
/**************************************************************/
.card .card-header.header-sm {
padding: 1rem 1.81rem;
height: auto;
}
/**************************************************************/
/**************************************************************/
/* CSS for ? */
/**************************************************************/
div.form-group.reset-secret {
background: rgba(243, 21, 6, 0.3);
outline: 1px solid red;
padding: 10px;
}
.bg-dropdown {
background-color: var(--dropdown-bg);
}
.hidden-input {
margin-left: -40px;
}
/**************************************************************/

View File

@ -11,222 +11,6 @@
*/
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700");
root,
:root.default {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: black;
--select-bg: #dee0f4;
--ram-bg: #323550;
--base-text: #b9c0d3;
--outline: #383e5d;
--card-banner-bg: #282a40;
--deep-bg: #1C1E2F;
--dropdown-bg: #222437;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #19d895;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
:root.anti-lockout {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: rgb(215, 82, 0);
--select-bg: #b8772c;
--ram-bg: #4d4d4e;
--base-text: white;
--outline: #c73929;
--card-banner-bg: #de7c26;
--deep-bg: #912f2f;
--dropdown-bg: #c83b3b;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #dbc900;
--secondary: #dde4eb;
--success: #adff84;
--info: #dbc900;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
:root.light {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: white;
--select-bg: #dee0f4;
--ram-bg: #4d4d4e;
--base-text: black;
--outline: #383e5d;
--card-banner-bg: #e0e0e0;
--deep-bg: #F5F5F5;
--dropdown-bg: #d8d8d8;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #449c4b;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
:root.dark {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: black;
--select-bg: #dee0f4;
--ram-bg: #3d3d3d;
--base-text: #b9c0d3;
--outline: #3f3f3f;
--card-banner-bg: #212221;
--deep-bg: #0d0d0d;
--dropdown-bg: #171717;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #19d895;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
:root.ronald {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: white;
--select-bg: #dee0f4;
--card-banner-bg: white;
--deep-bg: yellow;
--dropdown-bg: red;
--ram-bg: #4d4d4e;
--base-text: black;
--outline: #383e5d;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #19d895;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
*,
*::before,
*::after {

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,42 @@
:root.anti-lockout {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: rgb(215, 82, 0);
--select-bg: #b8772c;
--ram-bg: #4d4d4e;
--base-text: white;
--outline: #c73929;
--card-banner-bg: #de7c26;
--deep-bg: #912f2f;
--dropdown-bg: #c83b3b;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #dbc900;
--secondary: #dde4eb;
--success: #adff84;
--info: #dbc900;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@ -0,0 +1,33 @@
:root {
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: var(--red);
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: var(--teal);
--cyan: var(--cyan);
--white: var(--white);
--white-smoke: var(--white-smoke);
--gray: var(--gray);
--gray-dark: #292b2c;
--gray-light: #8ba2b5;
--gray-lightest: var(--gray-lightest);
--primary: var(--primary);
--secondary: var(--secondary);
--success: var(--success);
--info: var(--info);
--warning: var(--warning);
--danger: var(--danger);
--light: var(--light);
--dark: var(--dark);
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@ -0,0 +1,39 @@
/* 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;
}

View File

@ -0,0 +1,143 @@
/* Overlay (right click menu) */
.overlay {
display: none;
flex-direction: column;
background-color: var(--base-bg);
border-radius: 10px;
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
padding: 10px 0;
z-index: 10000;
overflow: scroll;
/* IE and Edge */
-ms-overflow-style: none;
/* Firefox */
scrollbar-width: none;
}
.overlay::-webkit-scrollbar {
display: none;
}
/* Position the content inside the overlay */
.overlay-content {
display: flex;
flex-direction: column;
background-color: var(--base-bg);
border-radius: 10px;
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
padding: 10px 0;
}
.overlay-title {
color: var(--info);
text-align: center;
padding: 3px;
}
/* The navigation links inside the overlay */
.overlay a {
font: inherit;
border: 0;
padding: 10px 30px 10px 15px;
width: 100%;
display: flex;
align-items: center;
position: relative;
text-decoration: unset;
color: var(--black);
font-weight: 500;
transition: 0.5s linear;
-webkit-transition: 0.5s linear;
-moz-transition: 0.5s linear;
-ms-transition: 0.5s linear;
-o-transition: 0.5s linear;
}
/* When you mouse over the navigation links, change their color */
.overlay a:hover,
.overlay a:focus {
background: var(--ram-bg);
color: var(--info);
}
/* Position the close button (top right corner) */
.overlay .closebtn .closebtn:hover {
background-color: var(--red);
color: var(--red);
z-index: 10000;
}
/* When the height of the screen is less than 450 pixels, change the font-size of the links and position the close button again, so they don't overlap */
@media screen and (max-height: 450px) {
.overlay a {
font-size: 20px;
}
.overlay .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}
.tree-file:hover {
cursor: pointer;
}
/* 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;
}
html,
body,
body>.container-scroller {
overflow: initial;
}
.editorManager {
top: 63px;
position: sticky;
}
span.separator {
width: 90%;
border-bottom: 2px solid var(--black);
margin: 0 auto;
}

View File

@ -0,0 +1,36 @@
/**************************************************************/
/* CSS for Page Footer */
/**************************************************************/
a:hover {
text-decoration: none;
}
.blink-text {
color: var(--base-text);
font-weight: bold;
font-size: 2rem;
animation: blinkingText 2s infinite;
}
@keyframes blinkingText {
0% {
color: var(--base-text);
}
50% {
color: var(--red);
}
100% {
color: var(--base-text);
}
}
#kofiframe {
border: none;
width: 100%;
padding: 4px;
background: var(--deep-bg);
}
/**************************************************************/

View File

@ -0,0 +1,44 @@
/**************************************************************/
/* CSS for the login page */
/**************************************************************/
.login-modal {
border-radius: 0.4rem !important;
box-shadow: 0 8px 12px 0 hsla(0, 0%, 0%, 0.2) !important;
}
.login-text-input {
border: none !important;
background-color: hsl(234, 30%, 45%);
color: var(--white) !important;
}
.login-text-input:hover,
.login-text-input:focus {
background-color: hsl(234, 30%, 39%) !important;
}
.login-input {
border-radius: 0.4rem !important;
box-shadow: 0 8px 12px 0 hsla(0, 0%, 0%, 0.2);
transition: all 0.3s ease-in-out;
}
.login-input:hover,
.login-input:focus {
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
}
#error-field {
color: var(--red);
text-align: center;
margin-bottom: 2rem;
}
.loginError {
color: var(--red);
text-align: center;
margin-bottom: 2rem;
}
/**************************************************************/

View File

@ -0,0 +1,59 @@
/**************************************************************/
/* CSS for Notifications */
/**************************************************************/
.notifications {
position: fixed;
width: 200px;
top: 70px;
right: 0px;
}
.notification {
position: relative;
margin-right: 1rem;
background: var(--card-banner-bg);
-webkit-transition: right 0.75s, opacity 0.75s, top 0.75s;
-moz-transition: right 0.75s, opacity 0.75s, top 0.75s;
-o-transition: right 0.75s, opacity 0.75s, top 0.75s;
transition: right 0.75s, opacity 0.75s, top 0.75s;
right: -20rem;
opacity: 0.1;
}
.toast-header {
background-color: var(--card-banner-bg);
color: var(--base-text);
}
.toast-body {
background-color: var(--dropdown-bg);
color: var(--base-text);
}
.notification img {
max-height: 20px;
}
.notification strong {
line-height: 20px;
}
.notification.active {
right: 0rem;
opacity: 1;
}
.notification.remove {
right: 0rem;
opacity: 0.1;
top: -2rem;
}
.notification span {
line-height: 20px;
font-size: 15px;
user-select: none;
cursor: pointer;
}
/**************************************************************/

View File

@ -0,0 +1,38 @@
#virt_console::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
#virt_console {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
white-space: pre-wrap;
width: 100%;
font-size: .8em;
padding: 5px 10px;
border: 1px solid var(--outline);
background-color: var(--card-banner-bg);
height: 500px;
overflow: scroll;
}
#to-bottom {
visibility: hidden;
float: right;
}
.term-btn {
max-width: 12rem;
white-space: nowrap;
}
div.server_command {
gap: 0.5rem;
}
input.server_command {
min-width: 10rem;
}

View File

@ -0,0 +1,108 @@
.api-alert {
position: absolute;
top: -5px;
left: 0;
font-size: 50px !important;
color: var(--white);
background: var(--blue);
/* rgb(0, 170, 170);*/
width: 100%;
height: 100%;
z-index: 100;
opacity: .95;
}
.api-alert p {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
text-align: center;
font-size: 20px;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
color: var(--white) !important;
}
.api-alert p>i,
.api-alert p>a {
color: var(--red);
}
button>i.refresh-class {
margin: 0px;
}
.refresh-class:hover {
cursor: grab;
}
.scroll {
max-height: 12em;
overflow-y: auto;
}
#overlay {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 100;
}
/* 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%);
}
div>.input-group>.custom-file-input {
position: relative !important;
-webkit-box-flex: 1 !important;
-ms-flex: 1 1 auto !important;
flex: 1 1 auto !important;
width: 1% !important;
margin-bottom: 0 !important;
border: 1px solid var(--outline);
}

View File

@ -0,0 +1,43 @@
:root.dark {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: black;
--select-bg: #dee0f4;
--ram-bg: #3d3d3d;
--base-bg: #303030;
--base-text: #b9c0d3;
--outline: #3f3f3f;
--card-banner-bg: #212221;
--deep-bg: #0d0d0d;
--dropdown-bg: #171717;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #19d895;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@ -0,0 +1,45 @@
root,
:root.default {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: black;
--base-bg: #dee0f4;
--select-bg: #dee0f4;
--ram-bg: #323550;
--base-text: #b9c0d3;
--outline: #383e5d;
--card-banner-bg: #282a40;
--deep-bg: #1C1E2F;
--dropdown-bg: #222437;
/*END THEME VARIATION*/
--black: black;
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #19d895;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@ -0,0 +1,43 @@
:root.light {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: white;
--select-bg: #dee0f4;
--base-bg: #e6e6e6;
--ram-bg: #4d4d4e;
--base-text: black;
--outline: #383e5d;
--card-banner-bg: #e0e0e0;
--deep-bg: #F5F5F5;
--dropdown-bg: #d8d8d8;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #449c4b;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@ -0,0 +1,42 @@
:root.ronald {
/*CHANGE THESE FOR THEMES*/
--tooltip-bg: white;
--select-bg: #dee0f4;
--card-banner-bg: white;
--deep-bg: yellow;
--dropdown-bg: red;
--ram-bg: #4d4d4e;
--base-text: black;
--outline: #383e5d;
/*END THEME VARIATION*/
--blue: #00aeef;
--indigo: #6610f2;
--purple: #ab8ce4;
--pink: #E91E63;
--red: #ff0017;
--orange: #fb9678;
--yellow: #ffd500;
--green: #3bd949;
--teal: #58d8a3;
--cyan: #57c7d4;
--white: #ffffff;
--white-smoke: #f3f5f6;
--gray: #6c757d;
--gray-light: #8ba2b5;
--gray-lightest: #f7f7f9;
--primary: #2196f3;
--secondary: #dde4eb;
--success: #19d895;
--info: #8862e0;
--warning: #ffaf00;
--danger: #ff6258;
--light: #fbfbfb;
--dark: #252C46;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@ -55,8 +55,8 @@ async function getTreeView(path, unzip = false, upload = false) {
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}
@ -87,8 +87,8 @@ function process_tree_response(response, unzip) {
<div id="${dpath}" data-path="${dpath}" data-name="${filename}" class="tree-caret tree-ctx-item tree-folder">
<input type="radio" class="root-input" name="root_path" value="${dpath}">
<span id="${dpath}span" class="files-tree-title" data-path="${dpath}" data-name="${filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
<i class="far fa-folder text-info"></i>
<i class="far fa-folder-open text-info"></i>
${filename}
</span>
</input></div><li>`

View File

@ -137,7 +137,7 @@ async function uploadFile(type, file = null, path = null, file_num = 0, _onProgr
// All promises resolved successfully
$("#upload_input").html(`<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value="${file.name}" type="text" id="file-uploaded" disabled></input> 🔒</div>`);
if (type === "import") {
document.getElementById("lower_half").style.visibility = "visible";
document.getElementById("lower_half").classList.remove("d-none");
document.getElementById("lower_half").hidden = false;
} else if (type === "background") {
setTimeout(function () {

View File

@ -17,8 +17,14 @@
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Sarabun" media="screen">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" />
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/css/internal/root.css">
<link rel="stylesheet" href="/static/assets/css/internal/anti-lockout.css">
<link rel="stylesheet" href="/static/assets/css/themes/default.css">
<link rel="stylesheet" href="/static/assets/css/base-style.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<link rel="stylesheet" href="/static/assets/css/crafty-toggle-btn.css">
<link rel="stylesheet" href="/static/assets/css/partial/crafty-notification.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
<meta name="mobile-web-app-capable" content="yes">
@ -37,12 +43,15 @@
<!-- End Plugin css for this page-->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
{% for theme in data['themes'] %}
<link rel="stylesheet" href="/static/assets/css/themes/{{ theme }}.css">
{% end %}
<link rel="stylesheet" href="/static/assets/css/base-style.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<!-- Alpine.js - The modern jQuery alternative -->
<script defer src="../static/assets/vendors/js/cdn.min.js"></script>
@ -69,21 +78,12 @@
<img src="/static/assets/images/logo_small.svg" alt="logo" /> </a>
</div>
<div class="navbar-menu-wrapper d-flex align-items-center">
<style>
body:not(.sidebar-icon-only) .navbar-toggler .mdi-chevron-double-right {
display: none;
}
body.sidebar-icon-only .navbar-toggler .mdi-chevron-double-left {
display: none;
}
</style>
<button class="navbar-toggler navbar-toggler align-self-center" type="button" data-toggle="minimize">
<span class="mdi mdi-chevron-double-left"></span>
<span class="mdi mdi-chevron-double-right"></span>
</button>
&nbsp;&nbsp;&nbsp;
<span class="badge-pill badge-outline-primary" id="server-name-nav" style="display: none;"></span>
<span class="badge-pill badge-outline-primary d-none" id="server-name-nav"></span>
{% include notify.html %}
@ -100,7 +100,7 @@
<div class="main-panel">
<div class="warnings">
<noscript class="noscript-warning" style="padding: 20px; background-color: rgb(247, 151, 15);">
<noscript class="noscript-warning">
<div>{% raw translate('base', 'doesNotWorkWithoutJavascript', data['lang']) %}</div>
</noscript>
</div>
@ -117,65 +117,6 @@
<!-- page-body-wrapper ends -->
</div>
<style>
.notifications {
position: fixed;
width: 200px;
top: 70px;
right: 0px;
}
.notification {
position: relative;
margin-right: 1rem;
background: var(--card-banner-bg);
-webkit-transition: right 0.75s, opacity 0.75s, top 0.75s;
-moz-transition: right 0.75s, opacity 0.75s, top 0.75s;
-o-transition: right 0.75s, opacity 0.75s, top 0.75s;
transition: right 0.75s, opacity 0.75s, top 0.75s;
right: -20rem;
opacity: 0.1;
}
.toast-header {
background-color: var(--card-banner-bg);
color: var(--base-text);
}
.toast-body {
background-color: var(--dropdown-bg);
color: var(--base-text);
}
.notification img {
max-height: 20px;
}
.notification strong {
line-height: 20px;
}
.notification.active {
right: 0rem;
opacity: 1;
}
.notification.remove {
right: 0rem;
opacity: 0.1;
top: -2rem;
}
.notification span {
line-height: 20px;
font-size: 15px;
user-select: none;
cursor: pointer;
}
</style>
<div class="notifications"></div>
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="default">
<head>
<!-- Required meta tags -->
@ -13,6 +13,7 @@
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome6/css/all.css">
<link rel="stylesheet" href="/static/assest/css/internal/root.css">
<link rel="stylesheet" href="/static/assest/css/crafty.css">
<link rel="manifest" href="/static/assets/crafty.webmanifest">
@ -24,14 +25,22 @@
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<link rel="stylesheet" href="/static/assets/css/internal/root.css">
<link rel="stylesheet" href="/static/assets/css/internal/anti-lockout.css">
{% for theme in data['themes'] %}
<link rel="stylesheet" href="/static/assets/css/themes/{{ theme }}.css">
{% end %}
<link rel="stylesheet" href="/static/assets/css/base-style.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head>
<body class="dark-theme">
<body>
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">

View File

@ -7,21 +7,6 @@
<span class="float-none float-sm-right d-block mt-1 mt-sm-0"><a target="_blank" class="blink-text" href="https://gitlab.com/crafty-controller/crafty-4/-/releases">Update Available!</a></span>
{% end %}
</div>
<style>
a:hover {
text-decoration: none;
}
.blink-text{
color: #000;
font-weight: bold;
font-size: 2rem;
animation: blinkingText 2s infinite;
}
@keyframes blinkingText{
0% { color: grey;}
50% { color: red;}
100% { color: grey;}
}
</style>
<link rel="stylesheet" href="/static/assets/css/partial/crafty-footer.css">
</footer>
<!-- partial -->

View File

@ -1,14 +1,6 @@
<!-- partial -->
<!-- partial:partials/_sidebar.html -->
<style>
@media screen and (max-width: 991px) {
.sidebar-offcanvas {
-webkit-transition: all 0.25s cubic-bezier(.22, .61, .36, 1);
transition: all 0.25s cubic-bezier(.22, .61, .36, 1);
box-shadow: 0px 8px 17px 2px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12), 0px 5px 5px -3px rgba(0, 0, 0, 0.2);
}
}
</style>
<script>
function debounce(func, wait, immediate) {
var timeout;
@ -71,8 +63,7 @@
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#page-layouts" aria-expanded="false"
aria-controls="page-layouts">
<a class="nav-link" data-toggle="collapse" href="#page-layouts" aria-expanded="false" aria-controls="page-layouts">
<i class="fas fa-server"></i> &nbsp;
<span class="menu-title">{{ translate('sidebar', 'servers', data['lang']) }}</span>
<i class="menu-arrow"></i>

View File

@ -6,8 +6,8 @@
text-danger
{% end %}
"></i><span id="notif-count" class="badge badge-notify"></span> </a>
<div class="dropdown-menu dropdown-menu-right navbar-dropdown notif-div" style="width: 40vw; max-height: 80vh;" aria-labelledby="notifDropdown">
<ul style="padding-top: 10px;" id="announcements">
<div class="p-0 dropdown-menu dropdown-menu-right navbar-dropdown notif-div vw-40 max-vh-80" aria-labelledby="notifDropdown">
<ul class="p-3" id="announcements">
</ul>
</div>
</li>
@ -38,7 +38,7 @@
<span class="dropdown-item" id="support_progress"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs',
data['lang']) }}<br><br></span>
<span class="dropdown-item" id="support_progress">
<div class="support_progress" style="height: 15px; width: 100%;">
<div class="support_progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="logs_progress_bar" role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
</div>
</span>
@ -96,7 +96,7 @@
console.log(data);
let text = "";
for (let value of data) {
text += `<li class="card-header header-sm justify-content-between align-items-center" id="${value.id}"><p style="float: right;"><i data-id="${value.id}"class="clear-button fa-regular fa-x"></i></p><a style="color: var(--purple);" href=${value.link} target="_blank"><h6>${value.title}</h6><small><p>${value.date}</p></small><p>${value.desc}</p></li></a>`
text += `<li class="card-header header-sm justify-content-between align-items-center annoucement-item" id="${value.id}"><p><i data-id="${value.id}" class="clear-button fa-regular fa-x"></i></p><a href=${value.link} target="_blank"><h6 class="annoucement-title">${value.title}</h6><small><p>${value.date}</p></small><p>${value.desc}</p></li></a>`
}
if (data.length > 0) {
localStorage.setItem("notif-count", data.length);
@ -104,8 +104,7 @@
$("#notif-count").html(data.length);
$("#announcements").html(text);
} else {
$("#announcements").html(`<p style='margin-top: 15px;' class='text-center'><i class="fa fa-bell-slash" aria-hidden="true"></i>
</p>`);
$("#announcements").html(`<li><p class='my-3 text-center'><i class="fa fa-bell-slash" aria-hidden="true"></i></p></li>`);
$("#notif-count").hide()
}
$(".clear-button").on("click", function (event) {
@ -119,8 +118,7 @@
localStorage.setItem("notif-count", notif_count);
$("#notif-count").html(notif_count);
} else {
$("#announcements").html(`<p style='margin-top: 15px;' class='text-center'><i class="fa fa-bell-slash" aria-hidden="true"></i>
</p>`)
$("#announcements").html(`<li><p class='my-3 text-center'><i class="fa fa-bell-slash" aria-hidden="true"></i></p></li>`)
$("#notif-count").html("");
}

View File

@ -25,15 +25,13 @@
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-history"></i> &nbsp;Audit Logs</h4>
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
{% end %}
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%">
<table class="table table-hover w-100" id="audit_table">
<thead>
<tr class="rounded">
<th>Time</th>
@ -46,8 +44,7 @@
<tbody>
<tr>
<td colspan="5" id="image-div" class="text-center"> <!-- Center image within table -->
<img class="img-center" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png"
alt="Crafty Logo, Crafty is loading" width="20%"><br><br>{{ translate('datatables',
<img class="img-center" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png" alt="Crafty Logo, Crafty is loading" width="20%"><br><br>{{ translate('datatables',
'loadingRecords', data['lang'])}}
</td>
</tr>
@ -59,16 +56,6 @@
</div>
</div>
</div>
<style>
.popover-body {
color: white !important;
;
}
</style>
</div>
<!-- content-wrapper ends -->

View File

@ -6,8 +6,7 @@
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
{% block content %}
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
<div class="content-wrapper">
@ -50,17 +49,16 @@
</div>
<!-- Page Title Header Ends-->
<form id="config-form" class="forms-sample" method="post" action="/panel/config_json">
<form id="config-form" method="post" action="/panel/config_json">
{% for item in data['config-json'].items() %}
{% if item[0] == "reset_secrets_on_next_boot" %}
<div class="form-group" style="background: rgba(243, 21, 6, 0.3); outline: 1px solid red; padding: 10px;">
<div class="form-group reset-secret">
{% else %}
<div class="form-group">
{% end %}
<label class="form" for="{{item[0]}}">{{item[0]}}
<small class="text-muted ml-1">
</small> </label><br />
<label class="form" for="{{item[0]}}">
{{item[0]}}
</label>
{% if item[0] == 'language' %}
<select name="{{item[0]}}" class="form-control">
{% for lang in data['availables_languages'] %}
@ -73,11 +71,9 @@
</select>
{% elif item[0] == 'disabled_language_files' %}
<div class="input-group">
<button type="button" class="btn btn-outline-default custom-picker"
onclick="$('option', $('#lang_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{
translate('panelConfig', 'enableLang', data['lang']) }}</button>
<select id="lang_select" class="form-control selectpicker show-tick custom-picker"
data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
<select id="lang_select" class="form-control selectpicker show-tick custom-picker" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for lang in data['all_languages'] %}
{% if lang in item[1] %}
<option selected>{{lang}}</option>
@ -86,17 +82,13 @@
{% end %}
{% end %}
</select>
<textarea id="disabled_lang" name="{{item[0]}}" class="form-control list hidden"
rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}"
hidden>{{','.join(item[1])}}</textarea>
<textarea id="disabled_lang" name="{{item[0]}}" class="form-control list hidden" rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}" hidden>{{','.join(item[1])}}</textarea>
</div>
{% elif item[0] == 'monitored_mounts'%}
<div class="input-group">
<button type="button" class="btn btn-outline-default custom-picker"
onclick="$('option', $('#mount_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#mount_select')).each(function(element) {$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')});">{{
translate('panelConfig', 'noMounts', data['lang']) }}</button>
<select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas"
data-tick-icon="fa-check" multiple data-style="custom-picker">
<select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for mount in data['all_partitions'] %}
{% if mount in item[1] %}
<option selected>{{mount}}</option>
@ -105,33 +97,40 @@
{% end %}
{% end %}
</select>
<textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden"
rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}"
hidden>{{','.join(item[1])}}</textarea>
<textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden" rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}" hidden>{{','.join(item[1])}}</textarea>
</div>
{% elif isinstance(item[1], list) %}
<textarea id="{{item[0]}}" value="{{','.join(item[1])}}" type="text" name="{{item[0]}}"
class="form-control list">{{','.join(item[1])}}</textarea>
<textarea id="{{item[0]}}" value="{{','.join(item[1])}}" type="text" name="{{item[0]}}" class="form-control list">{{','.join(item[1])}}</textarea>
{% elif isinstance(item[1], bool) %}
<div style="margin-left: 30px;">
{% if item[1] == True %}
<input type="radio" class="form-check-input" name="{{item[0]}}" id="True" value="True" checked>
 <label for="True">True</label><br>
<input type="radio" class="form-check-input" name="{{item[0]}}" id="False" value="False">
 <label for="False">False</label>
{% else %}
<input type="radio" class="form-check-input" name="{{item[0]}}" id="True" value="True">
 <label for="True">True</label><br>
<input type="radio" class="form-check-input" name="{{item[0]}}" id="False" value="False" checked>
 <label for="False">False</label>
{% end %}
<div class="custom-control custom-radio">
<input class="custom-control-input" type="radio" name="{{item[0]}}" id="{{item[0]}}_True" value="True" checked>
<label class="custom-control-label" for="{{item[0]}}_True">
True
</label>
</div>
<div class="custom-control custom-radio">
<input class="custom-control-input" type="radio" name="{{item[0]}}" id="{{item[0]}}_False" value="False">
<label class="custom-control-label" for="{{item[0]}}_False">False</label>
</div>
{% elif isinstance(item[1], int) %}
<input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}"
step="1" min="0" required>
{% else %}
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}"
step="2" min="0" required>
<div class="custom-control custom-radio">
<input class="custom-control-input" type="radio" name="{{item[0]}}" id="{{item[0]}}_True" value="True">
<label class="custom-control-label" for="{{item[0]}}_True">True</label>
</div>
<div class="custom-control custom-radio">
<input class="custom-control-input" type="radio" name="{{item[0]}}" id="{{item[0]}}_False" value="False" checked>
<label class="custom-control-label" for="{{item[0]}}_False">
False
</label>
</div>
{% end %}
{% elif isinstance(item[1], int) %}
<input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="1" min="0" required>
{% else %}
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="2" min="0" required>
{% end %}
</div>
{% end %}
@ -141,28 +140,7 @@
</div>
</div>
</div>
</div>
<style>
.dropdown-menu.inner {
display: inline-block !important;
}
.popover-body {
color: white !important;
;
}
input[type="radio"] {
-ms-transform: scale(1.5);
/* IE 9 */
-webkit-transform: scale(1.5);
/* Chrome, Safari, Opera */
transform: scale(1.5);
}
</style>
<!-- content-wrapper ends -->
{% end %}
@ -170,7 +148,7 @@
{% block js %}
<script>
function replacer(key, value) {
if (key == "disabled_language_files" || key == "monitored_mounts") {
if (key == "disabled_language_files" || key == "monitored_mounts" || key == "keywords") {
if (value == 0) {
return []
} else {
@ -199,6 +177,7 @@
formDataObject.disabled_language_files = $('#lang_select').val();
formDataObject.monitored_mounts = $('#mount_select').val();
formDataObject.keywords = $('#keywords').val().split(",");
$('#config-form input[type="radio"]:checked').each(function () {
if ($(this).val() == 'True') {
formDataObject[this.name] = true;
@ -206,12 +185,10 @@
formDataObject[this.name] = false;
}
});
console.log(formDataObject);
// Format the plain form data as JSON
let formDataJsonString = JSON.stringify(formDataObject, replacer);
console.log(formDataJsonString);
let res = await fetch(`/api/v2/crafty/config/`, {
method: 'PATCH',
headers: {

View File

@ -22,7 +22,7 @@
<div class="row">
<div class="col-md-4 grid-margin">
<div class="col-md-6 col-lg-4 grid-margin">
<div class="card">
<div class="card-body">
@ -37,13 +37,11 @@
</ul>
<br />
<div class="text-center">
<a href='https://ko-fi.com/C0C1AIIZS' target='_blank'><img style='border:0px;width:100%;'
src='../static/assets/images/kofi_cross.png' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
<a href='https://ko-fi.com/C0C1AIIZS' target='_blank'><img class="w-100" src='../static/assets/images/kofi_cross.png' alt='Buy Me a Coffee at ko-fi.com' /></a>
</div>
<br>
<div class="text-center">
<a href="https://www.patreon.com/bePatron?u=27882946"
data-patreon-widget-type="become-patron-button">Become a Patron!</a>
<a href="https://www.patreon.com/bePatron?u=27882946" data-patreon-widget-type="become-patron-button">Become a Patron!</a>
<script async src="https://c6.patreon.com/becomePatronButton.bundle.js"></script>
</div>
</div>
@ -51,7 +49,7 @@
</div>
</div>
</div>
<div class="col-md-4 grid-margin">
<div class="col-md-6 col-lg-4 grid-margin">
<div class="card">
<div class="card-body">
@ -63,17 +61,14 @@
</p>
<br />
<div class="text-center">
<iframe id='kofiframe'
src='https://ko-fi.com/arcadiatech/?hidefeed=true&widget=true&embed=true&preview=true'
style='border:none;width:100%;padding:4px;background:#1d1d2f;' height='650'
title='arcadiatech'></iframe>
<iframe id='kofiframe' src='https://ko-fi.com/arcadiatech/?hidefeed=true&widget=true&embed=true&preview=true' class="w-100 border-0" height='650' title='arcadiatech'></iframe>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 grid-margin">
<div class="col-md-6 col-lg-4 grid-margin">
<div class="card">
<div class="card-body">

View File

@ -23,69 +23,56 @@
<!-- Page Title Header Ends-->
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fa-solid fa-code-merge"></i> &nbsp;{{ translate('credits', 'developmentTeam',
data['lang'])
}}</h4>
<div class="card-header header-sm">
<h4><i class="fa-solid fa-code-merge"></i> &nbsp;{{ translate('credits', 'developmentTeam', data['lang'])}}</h4>
</div>
<div class="card-body">
<div class="row">
{% for person in data['staff']['development'] %}
<div class="col-lg-6 mb-5">
<div class="card rounded shadow-none">
<div class="row">
<div class="col-md-4" style="max-width: fit-content;">
<div class="user-avatar mb-auto">
<div class="col-12 col-lg-6">
<div class="row no-gutters border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col-12 col-sm-auto col-lg-12 col-xl-auto px-4 pb-2 pt-4 px-lg-4 pb-lg-2 pt-lg-4 py-sm-4 pr-sm-2 pl-sm-4 py-xl-4 pr-xl-2 pl-xl-4">
<div class="user-avatar pl-2 pb-2">
{% if person['pic'] %}
<img src="{{ person['pic'] }}" alt="profile image" class="profile-img img-lg rounded-circle">
{% else %}
<div alt="profil image" class="profile-img img-lg rounded-circle">
<img src="/static/assets/images/credits/user-circle-solid.svg" alt="profile image"
class="profile-img img-lg rounded-circle">
<img src="/static/assets/images/credits/user-circle-solid.svg" alt="profile image" class="profile-img img-lg rounded-circle">
</div>
{% end %}
</div>
<div class="wrapper">
<div class="wrapper d-flex align-items-center">
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
<div class="align-items-center">
<h4 class="font-weight-medium">{{ person['name'] }}</h4>
</div>
<div class="wrapper d-flex align-items-center font-weight-medium text-muted">
<div class="align-items-center font-weight-medium text-muted">
{% if person['loc'] %}
<i class="mdi mdi-map-marker-outline mr-2"></i>
<p class="mb-0 text-muted">{{ person['loc'] }}</p>
<p class="text-muted mb-2"><i class="mdi mdi-map-marker-outline"></i> {{ person['loc'] }}</p>
{% end %}
</div>
</div>
</div>
<div class="col-md-8">
<div class="wrapper d-flex align-items-start">
<div class="col px-4 pt-2 pb-4 px-lg-4 pt-lg-2 pb-lg-4 py-sm-4 pl-sm-2 pr-sm-4 py-xl-4 pl-xl-2 pr-xl-4 d-flex flex-column position-static">
<div name="tags" class="my-2 align-items-start">
{% if person['tags'][0] %}
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
<span class="btn btn-sm btn-info m-1">{{ person['tags'][0] }}</span>
{% end %}
{% if person['tags'][1] %}
{% if type(person['tags'][1]) is list %}
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0]
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary m-1">{{ person['tags'][1][0]
}}</a>
{% else %}
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
<span class="btn btn-sm btn-primary m-1">{{ person['tags'][1] }}</span>
{% end %}
{% end %}
{% if person['tags'][2] %}
{% if type(person['tags'][2]) is list %}
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success mr-2">{{
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success m-1">{{
person['tags'][2][0] }}</a>
{% else %}
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
<span class="btn btn-sm btn-inverse-success m-1">{{ person['tags'][2] }}</span>
{% end %}
{% end %}
</div>
<div class="wrapper align-items-start pt-3">
<div name="blurb" class="m-2 align-items-start">
{% if person['title'] %}
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
{% end %}
@ -93,8 +80,6 @@
</div>
</div>
</div>
</div>
</div>
{% end %}
</div>
@ -104,160 +89,133 @@
<br />
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fa fa-book"></i> &nbsp;{{ translate('credits', 'supportTeam', data['lang'])
}}</h4>
<div class="card-header header-sm">
<h4><i class="fa fa-book"></i> &nbsp;{{ translate('credits', 'supportTeam', data['lang']) }}</h4>
</div>
<div class="card-body">
<div class="row">
{% for person in data['staff']['support'] %}
<div class="col-lg-6 mb-5">
<div class="card rounded shadow-none">
<div class="row">
<div class="col-md-4" style="max-width: fit-content;">
<div class="user-avatar mb-auto">
<div class="col-12 col-lg-6">
<div class="row no-gutters border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col-12 col-sm-auto col-lg-12 col-xl-auto px-4 pb-2 pt-4 px-lg-4 pb-lg-2 pt-lg-4 py-sm-4 pr-sm-2 pl-sm-4 py-xl-4 pr-xl-2 pl-xl-4">
<div class="user-avatar pl-2 pb-2">
{% if person['pic'] %}
<img src="{{ person['pic'] }}" alt="profile image" class="profile-img img-lg rounded-circle">
{% else %}
<div alt="profil image" class="profile-img img-lg rounded-circle">
<img src="/static/assets/images/credits/user-circle-solid.svg" alt="profile image"
class="profile-img img-lg rounded-circle">
<img src="/static/assets/images/credits/user-circle-solid.svg" alt="profile image" class="profile-img img-lg rounded-circle">
</div>
{% end %}
</div>
<div class="wrapper">
<div class="wrapper d-flex align-items-center">
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
<div class="align-items-center">
<h4 class="font-weight-medium">{{ person['name'] }}</h4>
</div>
<div class="wrapper d-flex align-items-center font-weight-medium text-muted">
<div class="align-items-center font-weight-medium text-muted">
{% if person['loc'] %}
<i class="mdi mdi-map-marker-outline mr-2"></i>
<p class="mb-0 text-muted">{{ person['loc'] }}</p>
<p class="text-muted mb-2"><i class="mdi mdi-map-marker-outline"></i> {{ person['loc'] }}</p>
{% end %}
</div>
</div>
</div>
<div class="col-md-8">
<div class="wrapper d-flex align-items-start">
<div class="col px-4 pt-2 pb-4 px-lg-4 pt-lg-2 pb-lg-4 py-sm-4 pl-sm-2 pr-sm-4 py-xl-4 pl-xl-2 pr-xl-4 d-flex flex-column position-static">
<div name="tags" class="my-2 align-items-start">
{% if person['tags'][0] %}
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
<span class="btn btn-sm btn-info m-1">{{ person['tags'][0] }}</span>
{% end %}
{% if person['tags'][1] %}
{% if type(person['tags'][1]) is list %}
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0]
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary m-1">{{ person['tags'][1][0]
}}</a>
{% else %}
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
<span class="btn btn-sm btn-primary m-1">{{ person['tags'][1] }}</span>
{% end %}
{% end %}
{% if person['tags'][2] %}
{% if type(person['tags'][2]) is list %}
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success mr-2">{{
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success m-1">{{
person['tags'][2][0] }}</a>
{% else %}
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
<span class="btn btn-sm btn-inverse-success m-1">{{ person['tags'][2] }}</span>
{% end %}
{% end %}
</div>
<div class="wrapper align-items-start pt-3">
<div name="blurb" class="m-2 align-items-start">
{% if person['title'] %}
<h5><strong>{{ person['title'] }}</strong></h5>
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
{% end %}
<p>{{ person['blurb'] }}</p>
</div>
</div>
</div>
</div>
</div>
{% end %}
</div>
</div> <!-- end user row-->
</div> <!-- end of user row -->
</div>
<br />
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fa-solid fa-thumbtack"></i> &nbsp;{{ translate('credits', 'retiredStaff',
data['lang'])
}}</h4>
<div class="card-header header-sm">
<h4><i class="fa-solid fa-thumbtack"></i> &nbsp;{{ translate('credits', 'retiredStaff', data['lang']) }}</h4>
</div>
<div class="card-body">
<div class="row">
{% for person in data['staff']['retired'] %}
<div class="col-lg-6 mb-5">
<div class="card rounded shadow-none">
<div class="row">
<div class="col-md-4" style="max-width: fit-content;">
<div class="card-img-top user-avatar mb-auto">
<div class="col-12 col-lg-6">
<div class="row no-gutters border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col-12 col-sm-auto col-lg-12 col-xl-auto px-4 pb-2 pt-4 px-lg-4 pb-lg-2 pt-lg-4 py-sm-4 pr-sm-2 pl-sm-4 py-xl-4 pr-xl-2 pl-xl-4">
<div class="user-avatar pl-2 pb-2">
{% if person['pic'] %}
<img src="{{ person['pic'] }}" alt="profile image" class="profile-img img-lg rounded-circle">
{% else %}
<div alt="profil image" class="profile-img img-lg rounded-circle">
<img src="/static/assets/images/credits/user-circle-solid.svg" alt="profile image">
<img src="/static/assets/images/credits/user-circle-solid.svg" alt="profile image" class="profile-img img-lg rounded-circle">
</div>
{% end %}
</div>
<div class="wrapper">
<div class="wrapper d-flex align-items-center">
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
<div class="align-items-center">
<h4 class="font-weight-medium">{{ person['name'] }}</h4>
</div>
<div class="wrapper d-flex align-items-center font-weight-medium text-muted">
<div class="align-items-center font-weight-medium text-muted">
{% if person['loc'] %}
<i class="mdi mdi-map-marker-outline mr-2"></i>
<p class="mb-0 text-muted">{{ person['loc'] }}</p>
<p class="text-muted mb-2"><i class="mdi mdi-map-marker-outline"></i> {{ person['loc'] }}</p>
{% end %}
</div>
</div>
</div>
<div class="col-md-8">
<div class="wrapper d-flex align-items-start">
<div class="col px-4 pt-2 pb-4 px-lg-4 pt-lg-2 pb-lg-4 py-sm-4 pl-sm-2 pr-sm-4 py-xl-4 pl-xl-2 pr-xl-4 d-flex flex-column position-static">
<div name="tags" class="my-2 align-items-start">
{% if person['tags'][0] %}
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
<span class="btn btn-sm btn-info m-1">{{ person['tags'][0] }}</span>
{% end %}
{% if person['tags'][1] %}
{% if type(person['tags'][1]) is list %}
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0]
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary m-1">{{ person['tags'][1][0]
}}</a>
{% else %}
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
<span class="btn btn-sm btn-primary m-1">{{ person['tags'][1] }}</span>
{% end %}
{% end %}
{% if person['tags'][2] %}
{% if type(person['tags'][2]) is list %}
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success mr-2">{{
<a href="{{ person['tags'][2][1] }}" class="btn btn-sm btn-inverse-success m-1">{{
person['tags'][2][0] }}</a>
{% else %}
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
<span class="btn btn-sm btn-inverse-success m-1">{{ person['tags'][2] }}</span>
{% end %}
{% end %}
</div>
<div class="wrapper align-items-start pt-3">
<div name="blurb" class="m-2 align-items-start">
{% if person['title'] %}
<h5><strong>{{ person['title'] }}</strong></h5>
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
{% end %}
<p>{{ person['blurb'] }}</p>
</div>
</div>
</div>
</div>
</div>
{% end %}
</div> <!-- end user row-->
</div>
</div> <!-- end of user row -->
</div>
<br />
@ -274,7 +232,7 @@
<div class="card-body">
<p class="card-description"> {{ translate('credits', 'hugeDesc', data['lang']) }}
<code>{{ translate('credits', 'thankYou', data['lang']) }}</code>&nbsp; {{ translate('credits',
'patreonDesc', data['lang']) }} | <span style="color: #9365B8">{{ translate('credits', 'patreonUpdate',
'patreonDesc', data['lang']) }} | <span class="text-info">{{ translate('credits', 'patreonUpdate',
data['lang']) }} {{ data["lastUpdate"] }}</span>
</p>
<table class="table table-hover">
@ -341,14 +299,14 @@
{% for person in data['translations'] %}
<tr>
<td>{{ person }}</td>
<td class="pb-0">
<td class="">
<div class="row">
{% for language in data['translations'][person] %}
{% if language['status'] %}
<span class="btn btn-sm btn-inverse-success mr-2" style="margin-bottom: 12px;">{{ language['name']
<span class="btn btn-sm btn-inverse-success m-1">{{ language['name']
}}</span>
{% else %}
<span class="btn btn-sm btn-inverse-secondary mr-2" style="margin-bottom: 12px;">{{ language['name']
<span class="btn btn-sm btn-inverse-secondary m-1">{{ language['name']
}}</span>
{% end %}
{% end %}

View File

@ -62,14 +62,12 @@
<div class="form-group">
<div id="upload_input" class="input-group">
<div class="custom-file">
<input type="file" class="custom-file-input" id="file" name="file" multiple="false"
required>
<input type="file" class="custom-file-input" id="file" name="file" multiple="false" required>
<label id="fileLabel" class="custom-file-label" for="file">{{ translate('customLogin',
'labelLoginImage', data['lang']) }}</label>
</div>
<div class="input-group-append">
<button type="button" class="btn btn-info upload-button" id="upload-button"
onclick="uploadFile('background')" disabled>UPLOAD</button>
<button type="button" class="btn btn-info upload-button" id="upload-button" onclick="uploadFile('background')" disabled>UPLOAD</button>
</div>
</div>
</div>
@ -84,8 +82,7 @@
<div class="form-group row">
<label for="photo" class="col-sm-6 col-form-label">Selected Background Image</label>
<div class="col-sm-6">
<select class="form-select form-control form-control-lg select-css form-control-plaintext"
id="photo" name="photo" form="photo_form" onchange="updateBackgroundPreview()">
<select class="form-select form-control form-control-lg select-css form-control-plaintext" id="photo" name="photo" form="photo_form" onchange="updateBackgroundPreview()">
{% for image in data["backgrounds"] %}
<option value="{{image}}">{{image}}</option>
{% end %}
@ -94,9 +91,7 @@
</div>
<div id="photo_loading" class="form-group" hidden>
<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%">&nbsp;<i
class="fa-solid fa-spinner"></i></div>
<div class="progress-bar progress-bar-striped progress-bar-animated w-100" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">&nbsp;<i class="fa-solid fa-spinner"></i></div>
</div>
</div>
<div class="form-group row">
@ -104,13 +99,11 @@
data['lang']) }}</label>
<label class="col-sm-1" id="opacityValue">{{ data['login_opacity'] }}%</label>
<div class="range col-sm-8">
<input type="range" class="form-control-range" id="modal_opacity" name="modal_opacity"
onchange="previewOpacity()" min="0" max="100" value="{{ data['login_opacity'] }}">
<input type="range" class="form-control-range" id="modal_opacity" name="modal_opacity" onchange="previewOpacity()" min="0" max="100" value="{{ data['login_opacity'] }}">
</div>
</div>
<div id="login_preview" style="position: relative;">
<img id="bg-preview" src="../../static/assets/images/auth/{{ data['background'] }}"
class="img-fluid" alt="Responsive image">
<div id="login_preview" class="position-relative">
<img id="bg-preview" src="../../static/assets/images/auth/{{ data['background'] }}" class="img-fluid" alt="Responsive image">
<div id="login-form-preview">
<div id="login-form-background" class="auto-form-wrapper login-modal">
<div class="text-center auto-form-logo">
@ -174,20 +167,17 @@
</style>
<div id="login_form_data">
<input type="hidden" name="_xsrf"
value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
<input type="hidden" name="_xsrf" value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
<div class="form-group">
<label class="label">Username</label>
<div class="input-group">
<input type="text" class="form-control login-text-input login-input"
placeholder="Username" name="username" id="username" required="true" disabled>
<input type="text" class="form-control login-text-input login-input" placeholder="Username" name="username" id="username" required="true" disabled>
</div>
</div>
<div class="form-group">
<label class="label">Password</label>
<div class="input-group">
<input type="password" class="form-control login-text-input login-input"
placeholder="Password" name="password" id="password" required="true" disabled>
<input type="password" class="form-control login-text-input login-input" placeholder="Password" name="password" id="password" required="true" disabled>
</div>
</div>
<div class="form-group">
@ -195,20 +185,14 @@
In</button>
</div>
<fieldset style="color: red; text-align: center;">
<span></span>
</fieldset>
<div class="form-group d-flex justify-content-between">
<div class="form-check form-check-flat mt-0">
&nbsp;
</div>
<a href="#" class="text-small forgot-password" disabled>Forgot Password</a>
<a href="#" class="text-small forgot-password">Forgot Password</a>
</div>
<div class="text-block text-center my-3">
<span class="text-small font-weight-semibold"><a
href="https://craftycontrol.com/">Crafty Control
4.0.20</a> </span>
<span class="text-small font-weight-semibold"><a href="#">Crafty Control {{ data['version_data'] }}</a> </span>
</div>
</div>
</div>
@ -242,11 +226,6 @@
.img-fluid {
margin-bottom: 1rem;
}
.popover-body {
color: white !important;
;
}
</style>
<!-- content-wrapper ends -->
@ -325,8 +304,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
})
@ -349,8 +328,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
})

View File

@ -15,7 +15,7 @@
<div class="page-header">
<h4 class="page-title">{{ translate('dashboard', 'dashboard', data['lang']) }}
{% if data['server_stats']['running'] != 0 %}
<span id="sync" style="margin-left: 5px;"><i class="fas fa-sync fa-spin"></i></span>
<span id="sync" class="ml-5"><i class="fas fa-sync fa-spin"></i></span>
</h4>
{% end %}
</div>
@ -58,11 +58,13 @@
</div>
<div class="wrapper my-auto ml-auto ml-lg-4">
<p id="cpu_data" class="mb-0 text-success" data-toggle="tooltip" data-placement="top" data-html="true" title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}">
<p id="cpu_data" class="mb-0 text-success" data-toggle="tooltip" data-placement="top" data-html="true"
title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}">
{{ translate('dashboard', 'cpuUsage', data['lang']) }}: <span id="cpu_usage">{{
data.get('hosts_data').get('cpu_usage') }}</span>
</p>
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top" title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}">
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top"
title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}">
{{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{
data.get('hosts_data').get('mem_percent') }}%</span>
</p>
@ -101,7 +103,7 @@
{% if len(data['hosts_data']['disk_json']) > 0 %}
<div class="col-12 mt-4">
<div class="d-flex">
<div class="wrapper" style="width: 100%;">
<div class="wrapper w-100">
{% if len(data["monitored"]) > 0 %}
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'storage',
data['lang']) }}
@ -112,9 +114,11 @@
{% for item in data['hosts_data']['disk_json'] %}
{% if item["mount"] in data["monitored"] %}
<div id="{{item['device']}}" class="col-xl-3 col-lg-3 col-md-4 col-12">
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading" id="title_{{item['device']}}" data-toggle="tooltip" data-placement="bottom" title="{{item['mount']}}" style="max-width: 100%;"><i class="fas fa-hdd"></i>
<h4 class="mb-0 w-100 font-weight-semibold d-inline-block text-truncate storage-heading"
id="title_{{item['device']}}" data-toggle="tooltip" data-placement="bottom"
title="{{item['mount']}}"><i class="fas fa-hdd"></i>
{{item["mount"]}}</h4>
<div class="progress d-inline-block" style="height: 20px; width: 100%; background-color: rgb(139, 139, 139) !important;">
<div class="progress d-inline-block disk-usage">
<div class="progress-bar
{% if item['percent_used'] <= 58 %}
bg-success
@ -123,7 +127,9 @@
{% else %}
bg-danger
{% end %}
" role="progressbar" style="color: black; height: 100%; width: {{item['percent_used']}}%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">{{item["used"]}} /
" role="progressbar" style="width: {{item['percent_used']}}%"
aria-valuenow="{{item['percent_used']}}" aria-valuemin="0" aria-valuemax="100">
{{item["used"]}} /
{{item["total"]}}
</div>
</div>
@ -150,7 +156,9 @@
data['lang']) }}</h4>
{% if len(data['servers']) > 0 %}
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
{% end %}
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{
@ -159,7 +167,7 @@
<div class="card-body">
{% if len(data['servers']) == 0 and len(data['failed_servers']) == 0 %}
<div style="text-align: center; color: grey;">
<div class="text-align-center">
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
<br>
<h7>{{ translate('dashboard', 'no-servers', data['lang']) }} {{ translate('dashboard', 'newServer',
@ -188,7 +196,8 @@
<td draggable="false">
<i class="fas fa-server"></i>
{% if server['alert'] %}
<a style="color: red !important;" draggable="false" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<a class="server-alert" draggable="false"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
{{ server['server_data']['server_name'] }}&nbsp; <i class="fas fa-exclamation-triangle"></i>
</a>
{% else %}
@ -197,16 +206,17 @@
</a>
{% end %}
</td>
<td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% if server['user_command_permission'] %}
{% if server['stats']['importing'] and server['stats']['running'] %}
<!-- WHAT HAPPENED HERE -->
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'installing',
<a data-id="{{server['server_data']['server_id']}}" class=""><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'installing',
data['lang']) }}</i></a>
{% elif server['stats']['updating']%}
<!-- WHAT HAPPENED HERE -->
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'updating',
<a data-id="{{server['server_data']['server_id']}}" class=""><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'updating',
data['lang']) }}</i></a>
{% elif server['stats']['waiting_start']%}
<!-- WHAT HAPPENED HERE -->
@ -218,33 +228,39 @@
{{ translate('serverTerm', 'importing',
data['lang']) }}</a>
{% elif server['stats']['running'] %}
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" data-toggle="tooltip" title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip" title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip" title="{{ translate('dashboard', 'start' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
<i class="fas fa-play"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button" data-toggle="tooltip" title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}" class="clone_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% end %}
{% end %}
</td>
<td draggable="false" id="server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['cpu']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats']['cpu'] <= 33 %}
bg-success
@ -253,13 +269,14 @@
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['cpu']}}%"
aria-valuenow="{{server['stats']['cpu']}}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
</td>
<td draggable="false" id="server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['mem']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
@ -268,7 +285,8 @@
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%"
aria-valuenow="{{server['stats']['mem_percent']}}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['mem_percent']}}% -
@ -284,22 +302,16 @@
<td draggable="false" id="server_desc_{{server['server_data']['server_id']}}">
{% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
data['lang']) }} <br />
{% if server['stats']['desc'] != 'False' %}
<div id="desc_id" style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">{{ translate('serverStats', 'loadingMotd', data['lang']) }}</div> <br />
data['lang']) }}
{% end %}
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% end %}
<span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}"
data-serverId="{{server['server_data']['server_id']}}"
data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"
data-count="{{server['server_data']['count_players']}}"></span>
</td>
<td draggable="false">
<span class="port" data-toggle="tooltip" title="{{
<div id="server_running_status_{{server['server_data']['server_id']}}" data-toggle="tooltip" title="{{
server['server_data']['server_port'] }}">
<div id="server_running_status_{{server['server_data']['server_id']}}">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
@ -311,17 +323,14 @@
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span>
{% end %}
<br />
<br />
</td>
<span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}" data-serverId="{{server['server_data']['server_id']}}" data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}" data-count="{{server['server_data']['count_players']}}"></span>
</tr>
{% end %}
</div>
</span>
{% for server in data['failed_servers'] %}
<tr id="{{server['server_id']}}" draggable="false">
<td class="text-warning"><i class="fas fa-server"></i>&nbsp;<a class="text-warning" href="/panel/server_detail?id={{server['server_id']}}&subpage=config">{{server['server_name']}}</a>
<td class="text-warning"><i class="fas fa-server"></i>&nbsp;<a class="text-warning"
href="/panel/server_detail?id={{server['server_id']}}&subpage=config">{{server['server_name']}}</a>
</td>
<td></td>
<td></td>
@ -346,22 +355,28 @@
<div class="row">
<div class="col-10 col-lg-3 mx-0 px-0">
{% if server['alert'] %}
<a style="color: red !important" class="btn btn-link d-flex justify-content-start" type="button" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}&nbsp; <i class="fas fa-exclamation-triangle"></i>
<a class="btn btn-link d-flex justify-content-start server-alert" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}&nbsp; <i
class="fas fa-exclamation-triangle"></i>
</a>
{% else %}
<a class="btn btn-link d-flex justify-content-start" type="button" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<a class="btn btn-link d-flex justify-content-start" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}
</a>
{% end %}
</div>
<div class="col-2 col-lg-3 mx-0 px-0">
<a 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 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-chart-bar"></i>
</a>
</div>
<div class="col-4 col-lg-3 mx-0 px-0">
<a id="m_server_running_status_{{server['server_data']['server_id']}}" class="btn btn-link d-flex justify-content-start" type="button">
<a id="m_server_running_status_{{server['server_data']['server_id']}}"
class="btn btn-link d-flex justify-content-start" type="button">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
@ -382,17 +397,23 @@
{% if server['stats']['running'] %}
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link stop_button actions_serveritem" data-toggle="tooltip" title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link stop_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link restart_button actions_serveritem" data-toggle="tooltip" title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link restart_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link kill_button actions_serveritem" data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a>
</div>
@ -418,24 +439,31 @@
{% elif server['stats']['importing']%}
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link"><i class="fa fa-spinner fa-spin"></i>
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link"><i
class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'importing', data['lang']) }}</a>
</div>
</div>
{% else %}
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn play_button actions_serveritem" data-toggle="tooltip" title="{{ translate('dashboard', 'start' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}"
class="btn play_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
<i class="fas fa-play"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn clone_button actions_serveritem" data-toggle="tooltip" title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}"
class="btn clone_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn kill_button actions_serveritem" data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<a data-id="{{server['server_data']['server_id']}}"
class="btn kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i></a>
</div>
</div>
@ -447,13 +475,15 @@
</h2>
</div>
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse" aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse"
aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
<div class="card-body">
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</h6>
<div id="m_server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['cpu']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats']['cpu'] <= 33 %}
bg-success
@ -462,7 +492,8 @@
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['cpu']}}%"
aria-valuenow="{{server['stats']['cpu']}}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
</div>
@ -470,7 +501,8 @@
<div class="col-6">
<h6>{{ translate('dashboard', 'memUsage', data['lang']) }}</h6>
<div draggable="false" id="m_server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['mem']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
@ -479,7 +511,9 @@
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%"
aria-valuenow="{{server['stats']['mem_percent']}}" aria-valuemin="0" aria-valuemax="100">
</div>
</div>
{{server['stats']['mem_percent']}}% -
@ -499,22 +533,12 @@
{{ server['stats']['world_size'] }}
</div>
</div>
<div class="col-6" style="width: auto;">
<div class="col-6">
<h6>{{ translate('dashboard', 'players', data['lang']) }}</h6>
<div draggable="false" id="m_server_desc_{{server['server_data']['server_id']}}">
{% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard',
'max',
data['lang']) }} <br />
{% if server['stats']['desc'] != 'False' %}
<div id="desc_id" style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">
{{ server['stats']['desc'] }}</div> <br />
{% end %}
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
data['lang']) }}
{% end %}
</div>
</div>
@ -535,30 +559,6 @@
</div>
<!-- content-wrapper ends -->
<div id="mobile"></div>
<style>
.popover-body {
color: white !important;
;
}
#desc_id {
-ms-overflow-style: none;
/* for Internet Explorer, Edge */
scrollbar-width: none;
/* for Firefox */
overflow-y: scroll;
}
#desc_id::-webkit-scrollbar {
display: none;
/* for Chrome, Safari, and Opera */
}
.gray {
color: gray !important;
}
</style>
{% end %}
@ -617,8 +617,8 @@
setTimeout(function () {
$('.modal').modal('hide');
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}, 2000)
}
@ -726,8 +726,8 @@
if (server.int_ping_results) {
/* Update Players */
if (server.players) {
server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
m_server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}`
m_server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}`
server_players.setAttribute('data-players', server.online);
server_players.setAttribute('data-max', server.max);
@ -756,22 +756,8 @@
server_infos = "";
m_server_infos = "";
server_infos = server.online + " / " + server.max + " {{ translate('dashboard', 'max', data['lang']) }}<br />"
}
/* Update Motd */
let motd = "";
if (server.desc) {
m_motd = `<span id="m_input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span>`;
motd = `<span id="input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span>`;
m_server_infos = server_infos + '<div id="desc_id" style="word-wrap: break-word; overflow: auto;">' + motd + '</div>' + "<br />";
server_infos = server_infos + '<div id="desc_id" style="word-wrap: break-word; max-width: 85px !important; overflow: auto;">' + motd + '</div>' + "<br />";
}
/* Version */
if (server.version) {
server_infos = server_infos + server.version
m_server_infos = m_server_infos + server.version
server_infos = server.online + " / " + server.max + " {{ translate('dashboard', 'max', data['lang']) }}"
m_server_infos = server.online + " / " + server.max + " {{ translate('dashboard', 'max', data['lang']) }}"
}
server_desc.innerHTML = server_infos;
m_server_desc.innerHTML = m_server_infos;
@ -880,8 +866,8 @@
for (i = 0; i < hostStats.disk_usage.length; i++) {
if (hostStats.mounts.includes(hostStats.disk_usage[i].mount)) {
storage_html += `<div id="host_storage" class="col-xl-3 col-lg-3 col-md-4 col-12">
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading" id="title_host_storage" data-toggle="tooltip" data-placement="bottom" title="${hostStats.disk_usage[i].mount}" style="max-width: 100%;"><i class="fas fa-hdd"></i> ${hostStats.disk_usage[i].mount}</h4>
<div class="progress" style="display: inline-block; height: 20px; width: 100%; background-color: rgb(139, 139, 139) !important;">
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading mw-100" id="title_host_storage" data-toggle="tooltip" data-placement="bottom" title="${hostStats.disk_usage[i].mount}"><i class="fas fa-hdd"></i> ${hostStats.disk_usage[i].mount}</h4>
<div class="progress d-inline-block disk-usage">
<div class="progress-bar`;
if (hostStats.disk_usage[i].percent_used <= 58) {
storage_html += ` bg-success`;
@ -890,7 +876,7 @@
} else {
storage_html += ` bg-danger`;
}
storage_html += `" role="progressbar" style="color: black; height: 100%; width: ${hostStats.disk_usage[i].percent_used}%;"
storage_html += `" role="progressbar" style="width: ${hostStats.disk_usage[i].percent_used}%;"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">${hostStats.disk_usage[i].used} / ${hostStats.disk_usage[i].total}
</div>
</div>
@ -1024,8 +1010,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="default">
<head>
<!-- Required meta tags -->
<meta charset="utf-8" />
@ -22,7 +23,10 @@
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css" />
<link rel="stylesheet" href="/static/assest/css/internal/root.css">
<link rel="stylesheet" href="/static/assets/css/themes/default.css">
<link rel="stylesheet" href="/static/assets/css/base-style.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg" />
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
@ -35,7 +39,7 @@
}
</style>
<body class="dark-theme">
<body>
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
@ -100,4 +104,5 @@
});
</script>
</body>
</html>

View File

@ -7,22 +7,16 @@
{% block content %}
<div class="content-wrapper">
<div class="card-header justify-content-between align-items-center" style="border: none;">
<div id="image-div" style="width: 100%;">
<img class="img-center" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png"
alt="Crafty Logo, Crafty is loading" width="20%" style="clear: both;">
<div class="card-header justify-content-between align-items-center border-0">
<div id="image-div" class="w-100">
<img class="img-center clearfix" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png" alt="Crafty Logo, Crafty is loading" width="20%">
</div>
<br>
</br>
<div id="text-div" style="width: 100%; text-align: center;">
<h2 id="status" style="display: block;" data-init="{{ translate('startup', 'serverInit', data['lang']) }}"
data-server="{{ translate('startup', 'server', data['lang']) }}"
data-internet="{{ translate('startup', 'internet', data['lang']) }}"
data-tasks="{{ translate('startup', 'tasks', data['lang']) }}"
data-internals="{{ translate('startup', 'internals', data['lang']) }}"
data-almost="{{ translate('startup', 'almost', data['lang']) }}"
data-cache="{{ translate('startup', 'cache', data['lang'])}}">
{{ translate('startup', 'starting', data['lang']) }}</h2>
<div id="text-div" class="w-100 text-center">
<h2 id="status" class="d-block" data-init="{{ translate('startup', 'serverInit', data['lang']) }}" data-server="{{ translate('startup', 'server', data['lang']) }}" data-internet="{{ translate('startup', 'internet', data['lang']) }}" data-tasks="{{ translate('startup', 'tasks', data['lang']) }}" data-internals="{{ translate('startup', 'internals', data['lang']) }}" data-almost="{{ translate('startup', 'almost', data['lang']) }}" data-cache="{{ translate('startup', 'cache', data['lang'])}}">
{{ translate('startup', 'starting', data['lang']) }}
</h2>
</div>
</div>

View File

@ -24,7 +24,7 @@
<div class="row">
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
<div class="card-body pt-0">
{% if data['superuser'] %}
@ -79,7 +79,8 @@
<tbody>
{% for user in data['users'] %}
<tr>
<td><i class="fas fa-user"></i><span id="user_{{user.user_id}}">{{ user.username }}</span></td>
<td><i class="fas fa-user"></i><span id="user_{{user.user_id}}">{{ user.username }}</span>
</td>
<td>
{% if user.enabled %}
<span class="text-success">
@ -106,9 +107,18 @@
{% end %}
</ul>
</td>
<td><span data-translate="{{translate('userConfig', 'userName', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'userName', data['lang'])}}" id="username_{{user.user_id}}" class="edit_user clickable" data-name="{{user.username}}" data-id="{{user.user_id}}"><i class="fa-solid fa-user"></i></span>
&nbsp;&nbsp;<span data-translate1="{{translate('userConfig', 'password', data['lang'])}}" data-translate2="{{translate('userConfig', 'repeat', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'password', data['lang'])}}" class="edit_password clickable" data-id="{{user.user_id}}"><i class="fa-solid fa-lock"></i></span>
&nbsp;&nbsp;<a data-toggle="tooltip" title="{{ translate('userConfig', 'pageTitle', data['lang'])}}" href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a>
<td><span data-translate="{{translate('userConfig', 'userName', data['lang'])}}"
data-toggle="tooltip" title="{{ translate('userConfig', 'userName', data['lang'])}}"
id="username_{{user.user_id}}" class="edit_user clickable" data-name="{{user.username}}"
data-id="{{user.user_id}}"><i class="fa-solid fa-user"></i></span>
&nbsp;&nbsp;<span data-translate1="{{translate('userConfig', 'password', data['lang'])}}"
data-translate2="{{translate('userConfig', 'repeat', data['lang'])}}"
data-toggle="tooltip" title="{{ translate('userConfig', 'password', data['lang'])}}"
class="edit_password clickable" data-id="{{user.user_id}}"><i
class="fa-solid fa-lock"></i></span>
&nbsp;&nbsp;<a data-toggle="tooltip"
title="{{ translate('userConfig', 'pageTitle', data['lang'])}}"
href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a>
</td>
</tr>
{% end %}
@ -141,9 +151,18 @@
{% end %}
</ul>
</td>
<td><span data-translate="{{translate('userConfig', 'userName', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'userName', data['lang'])}}" id="username_{{user.user_id}}" class="edit_user clickable" data-name="{{user.username}}" data-id="{{user.user_id}}"><i class="fa-solid fa-user"></i></span>
&nbsp;&nbsp;<span data-translate1="{{translate('userConfig', 'password', data['lang'])}}" data-translate2="{{translate('userConfig', 'repeat', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'password', data['lang'])}}" class="edit_password clickable" data-id="{{user.user_id}}"><i class="fa-solid fa-lock"></i></span>
&nbsp;&nbsp;<a data-toggle="tooltip" title="{{ translate('userConfig', 'pageTitle', data['lang'])}}" href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a>
<td><span data-translate="{{translate('userConfig', 'userName', data['lang'])}}"
data-toggle="tooltip" title="{{ translate('userConfig', 'userName', data['lang'])}}"
id="username_{{user.user_id}}" class="edit_user clickable" data-name="{{user.username}}"
data-id="{{user.user_id}}"><i class="fa-solid fa-user"></i></span>
&nbsp;&nbsp;<span data-translate1="{{translate('userConfig', 'password', data['lang'])}}"
data-translate2="{{translate('userConfig', 'repeat', data['lang'])}}"
data-toggle="tooltip" title="{{ translate('userConfig', 'password', data['lang'])}}"
class="edit_password clickable" data-id="{{user.user_id}}"><i
class="fa-solid fa-lock"></i></span>
&nbsp;&nbsp;<a data-toggle="tooltip"
title="{{ translate('userConfig', 'pageTitle', data['lang'])}}"
href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a>
</td>
</tr>
{% end %}
@ -283,18 +302,15 @@
.clickable {
color: var(--primary);
}
.clickable:hover {
cursor: pointer;
}
.custom-picker {
border: 1px solid var(--outline);
}
.popover-body {
color: white !important;
;
}
.loading:after {
overflow: hidden;
display: inline-block;
@ -373,7 +389,7 @@
bootbox.alert({
title: responseData.status,
message: responseData.error
message: responseData.error_data
});
}
}
@ -410,12 +426,13 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}
});
}
)
});
if (webSocket) {
webSocket.on('move_status', function (message) {
@ -451,8 +468,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
});

View File

@ -122,7 +122,7 @@ data['lang']) }}{% end %}
name="lang" form="user_form">
{% for lang in data['languages'] %}
{% if not 'incomplete' in lang %}
<option value="{{lang}}" >{{translate('language', lang, 'humanized_index')}}</option>
<option value="{{lang}}" >{{translate('language', lang, 'humanized_index', False)}}</option>
{% else %}
<option value="{{lang}}" disabled>{{lang}}</option>
{% end %}
@ -547,7 +547,7 @@ data['lang']) }}{% end %}
bootbox.alert({
title: responseData.error,
message: responseData.error
message: responseData.error_data
});
}
}

View File

@ -31,13 +31,11 @@
<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" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config"
role="tab" aria-selected="false">
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="false">
<i class="fas fa-cogs"></i>{{ translate('apiKeys', 'config', data['lang']) }}</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}"
role="tab" aria-selected="true">
<a class="nav-link active" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}" role="tab" aria-selected="true">
<i class="fas fa-key"></i>{{ translate('apiKeys', 'apiKeys', data['lang']) }}</a>
</li>
</ul>
@ -86,13 +84,9 @@
apikey.server_permissions }}
{{ translate('apiKeys', 'crafty', data['lang']) }} {{
apikey.crafty_permissions }}</td>
<td><button class="btn btn-danger delete-api-key"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}">{{translate('panelConfig',
<td><button class="btn btn-danger delete-api-key" data-key-id="{{ apikey.token_id }}" data-key-name="{{ apikey.name }}">{{translate('panelConfig',
'delete', data['lang'])}}</button>
<button class="btn btn-outline-primary get-a-token"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}">{{translate('apiKeys',
<button class="btn btn-outline-primary get-a-token" data-key-id="{{ apikey.token_id }}" data-key-name="{{ apikey.name }}">{{translate('apiKeys',
'getToken', data['lang'])}}</button>
</td>
</tr>
@ -118,8 +112,7 @@
<label class="form-label" for="username">{{ translate('apiKeys', 'name',
data['lang']) }}<small class="text-muted ml-1"> - {{
translate('apiKeys', 'nameDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="name" id="name"
placeholder="API Key">
<input type="text" class="form-control" name="name" id="name" placeholder="API Key">
</div>
<table class="table table-hover mb-3">
@ -136,9 +129,7 @@
}}</label>
</td>
<td>
<input type="checkbox" class="server_perm"
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
<input type="checkbox" class="server_perm" id="permission_{{ permission.name }}" name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
@ -149,13 +140,9 @@
</td>
<td>
{% if permission in data['user_crafty_permissions'] %}
<input type="checkbox" class="crafty_perm"
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
<input type="checkbox" class="crafty_perm" id="permission_{{ permission.name }}" name="permission_{{ permission.name }}" value="1">
{% else %}
<input type="checkbox" class="crafty_perm"
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1" disabled>
<input type="checkbox" class="crafty_perm" id="permission_{{ permission.name }}" name="permission_{{ permission.name }}" value="1" disabled>
{% end %}
</td>
</tr>
@ -306,7 +293,7 @@
if (responseData.status === "ok") {
bootbox.alert({
title: `API token for ${keyName}`,
message: `Here is an API token for ${keyName}:\n<pre style="white-space: pre-wrap;color:white;word-break:break-all;background: grey;border-radius: 5px;">${responseData.data}</pre>`
message: `Here is an API token for ${keyName}:\n<pre class="api-key">${responseData.data}</pre>`
});
} else {

View File

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

View File

@ -3,7 +3,7 @@
<div class="card">
<div class="card-body pt-3 pb-3">
<div class="row">
<div class="col-sm-4 mr-2">
<div class="col-sm-4">
{% if data['server_stats']['running'] %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }}</span><br />
@ -21,7 +21,7 @@
<b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{ data['serverTZ'] }}</span>
</div>
<div class="col-sm-3 mr-2">
<div class="col-sm-3">
<b>{{ translate('serverStats', 'cpuUsage', data['lang']) }}:</b> <span id="cpu">{{ data['server_stats']['cpu'] }}%</span> <br />
<b>{{ translate('serverStats', 'memUsage', data['lang']) }}:</b> <span id="mem">{{ data['server_stats']['mem'] }}</span> <br />
{% if data['server_stats']['int_ping_results'] %}
@ -31,14 +31,14 @@
{% end %}
</div>
<div class="col-sm-3 mr-2">
<div class="col-sm-5">
{% if data['server_stats']['version'] != 'False' %}
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> <span id="version">{{ data['server_stats']['version'] }}</span><br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" style="max-width: 10px; max-height: 10px" class="input_motd">{{ translate('serverStats', 'loadingMotd', data['lang']) }}</span>
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ translate('serverStats', 'loadingMotd', data['lang']) }}</span>
<br />
{% else %}
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> <span id="version">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span style="max-width: 10px; max-height: 10px" id="input_motd" class="input_motd">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
{% end %}
<b>Server Type: <span class="text-info">{{data['server_stats']['server_type']}}</span></b>
@ -211,17 +211,24 @@
let text = ""
let players = server.players_cache;
for (let i = 0; i < players.length; i++) {
text += `<tr id="playerItem-${players[i]["name"]}" class="playerItem--" style="text-align: center;">`;
text += `<td class="no-scroll" style="overflow: scroll;"><strong>${players[i]["name"]}</strong></td>`;
text += `<tr id="playerItem-${players[i]["name"]}" class="playerItem-- text-center">`;
text += `<td class="no-scroll col-5"><strong>${players[i]["name"]}</strong></td>`;
if (players[i]["status"] === "Online") {
text += `<td><span class="text-success"><i class="fas fa-signal"></i> ${players[i]['status']}</span></td>`
text += `<td class="col-4"><span class="text-success"><i class="fas fa-signal"></i> ${players[i]['status']}</span></td>`
} else {
text += `<td><span class="text-warning"><i class="fa-regular fa-circle-xmark"></i><span class="offline-status">&nbsp;${players[i]['status']}</span><span class="conn-break"> Last connection :<br> ${players[i]['last_seen']}</span></td>`
text += `<td class="col-4"><span class="text-warning"><i class="fa-regular fa-circle-xmark"></i><span class="offline-status">&nbsp;${players[i]['status']}</span><span class="conn-break"> Last connection :<br> ${players[i]['last_seen']}</span></td>`
}
if (server["running"]) {
text += `<td><button onclick="send_command_to_server('ban ${players[i]['name']}')" type="button" class="btn btn-danger controls">Ban</button><br class="mobile-break"><button onclick="send_command_to_server('kick ${players[i]['name']}')" type="button" class="btn btn-outline-danger controls">Kick</button><br><button onclick="send_command_to_server('op ${players[i]['name']}')" type="button" class="btn btn-warning controls">OP</button><br class="mobile-break"><button onclick="send_command_to_server('deop ${players[i]['name']}')" type="button" class="btn btn-outline-warning controls">De-OP</button></td>`
text += `<td class="col-3">
<div class="row">
<div class="col"><button onclick="send_command_to_server('ban ${players[i]['name']}')" type="button" class="btn btn-danger">Ban</button></div>
<div class="col"><button onclick="send_command_to_server('kick ${players[i]['name']}')" type="button" class="btn btn-outline-danger">Kick</button></div>
<div class="col"><button onclick="send_command_to_server('op ${players[i]['name']}')" type="button" class="btn btn-warning">OP</button></div>
<div class="col"><button onclick="send_command_to_server('deop ${players[i]['name']}')" type="button" class="btn btn-outline-warning">De-OP</button></div>
</div>
</td>`
} else {
text += `<td><span> Unavailable<br> (Server Offline)</span></td>`
text += `<td><div><span> Unavailable<br> (Server Offline)</span></div></td>`
}
}

View File

@ -16,7 +16,7 @@
{% end %}
{% if data['permissions']['Backup'] in data['user_permissions'] %}
{% if data['backup_failed'] %}
<a style="color: red !important;" class="dropdown-item {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false"><i class="fas fa-save"></i> {{ translate('serverDetails', 'backup', data['lang']) }}&nbsp; <i class="fas fa-exclamation-triangle"> </i></a>
<a class="dropdown-item text-danger {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false"><i class="fas fa-save"></i> {{ translate('serverDetails', 'backup', data['lang']) }}&nbsp; <i class="fas fa-exclamation-triangle"> </i></a>
{% else %}
<a class="dropdown-item {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false"><i class="fas fa-save"></i> {{ translate('serverDetails', 'backup', data['lang']) }}</a>
{% end %}

View File

@ -21,7 +21,7 @@
{% if data['permissions']['Backup'] in data['user_permissions'] %}
{% if data['backup_failed'] %}
<li class="nav-item term-nav-item">
<a style="color: red !important;" class="nav-link {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
<a class="nav-link text-danger {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
<i class="fas fa-save"></i>{{ translate('serverDetails', 'backup', data['lang']) }}&nbsp; <i class="fas fa-exclamation-triangle"> </i></a>
</li>
{% else %}

View File

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

View File

@ -40,31 +40,26 @@
{% include "parts/m_server_controls_list.html %}
</span>
<div class="row">
<div class="col-md-12 col-sm-12" style="overflow-x:auto;">
<div class="col-md-12 col-sm-12 overflow-x-auto">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fa-regular fa-bell"></i> {{ translate('serverBackups', 'backups',
data['lang']) }} </h4>
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" ,
data-placement="bottom"></span>
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" , data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" , data-placement="bottom"></span>
{% end %}
<div><a class="btn btn-info"
href="/panel/add_backup?id={{ data['server_stats']['server_id']['server_id'] }}"><i
class="fas fa-plus-circle"></i> {{ translate('serverBackups', 'newBackup', data['lang']) }}</a>
<div><a class="btn btn-info" href="/panel/add_backup?id={{ data['server_stats']['server_id']['server_id'] }}"><i class="fas fa-plus-circle"></i> {{ translate('serverBackups', 'newBackup', data['lang']) }}</a>
</div>
</div>
<div class="card-body">
{% if len(data['backups']) == 0 %}
<div style="text-align: center; color: grey;">
<div class="text-align-center text-secondary">
<h7>{{ translate('serverBackups', 'no-backup', data['lang']) }}.</h7>
</div>
{% end %}
{% if len(data['backups']) > 0 %}
<div class="d-none d-lg-block">
<table class="table table-hover responsive-table" aria-label="backups list" id="backup_table"
style="table-layout:fixed;">
<table class="table table-hover responsive-table" aria-label="backups list" id="backup_table" style="table-layout:fixed;">
<thead>
<tr class="rounded">
<th scope="col" style="width: 15%; min-width: 10px;">{{ translate('serverBackups', 'name',
@ -87,28 +82,22 @@
<br>
{% if backup.default %}
<span class="badge-pill badge-outline-warning">{{ translate('serverBackups', 'default',
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain"
data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i
class="fa-solid fa-question"></i></button></small>
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain" data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i class="fa-solid fa-question"></i></button></small>
{% end %}
</td>
<td>
<div id="{{backup.backup_id}}_status">
<button class="btn btn-outline-success backup-status" data-status="{{ backup.status }}"
data-Standby="{{ translate('serverBackups', 'standby', data['lang'])}}"
data-Failed="{{ translate('serverBackups', 'failed', data['lang'])}}"></button>
<button class="btn btn-outline-success backup-status" data-status="{{ backup.status }}" data-Standby="{{ translate('serverBackups', 'standby', data['lang'])}}" data-Failed="{{ translate('serverBackups', 'failed', data['lang'])}}"></button>
</div>
</td>
<td id="{{backup.backup_location}}" class="type">
<p style="overflow: scroll;" class="no-scroll">{{backup.backup_location}}</p>
<p class="no-scroll">{{backup.backup_location}}</p>
</td>
<td id="{{backup.max_backups}}" class="trigger" style="overflow: scroll; max-width: 30px;">
<td id="{{backup.max_backups}}" class="trigger overflow-scroll" style="max-width: 30px;">
<p>{{backup.max_backups}}</p>
</td>
<td id="backup_edit" class="action">
<button
onclick="window.location.href=`/panel/edit_backup?id={{ data['server_stats']['server_id']['server_id'] }}&backup_id={{backup.backup_id}}`"
class="btn btn-info">
<button onclick="window.location.href=`/panel/edit_backup?id={{ data['server_stats']['server_id']['server_id'] }}&backup_id={{backup.backup_id}}`" class="btn btn-info">
<i class="fas fa-pencil-alt"></i>
</button>
{% if not backup.default %}
@ -116,9 +105,7 @@
<i class="fas fa-trash" aria-hidden="true"></i>
</button>
{% end %}
<button data-backup={{ backup.backup_id }} data-toggle="tooltip"
title="{{ translate('serverBackups', 'run', data['lang']) }}"
class="btn btn-outline-warning run-backup backup_now_button">
<button data-backup={{ backup.backup_id }} data-toggle="tooltip" title="{{ translate('serverBackups', 'run', data['lang']) }}" class="btn btn-outline-warning run-backup backup_now_button">
<i class="fa-solid fa-forward"></i>
</button>
</td>
@ -128,8 +115,7 @@
</table>
</div>
<div class="d-block d-lg-none">
<table aria-label="backups list" class="table table-hover responsive-table" id="backup_table_mini"
style="table-layout:fixed;">
<table aria-label="backups list" class="table table-hover responsive-table" id="backup_table_mini" style="table-layout:fixed;">
<thead>
<tr class="rounded">
<th style="width: 40%; min-width: 10px;">Name
@ -145,22 +131,16 @@
<p>{{backup.backup_name}}</p>
<br>
<div id="{{backup.backup_id}}_status">
<button class="btn btn-outline-success backup-status" data-status="{{ backup.status }}"
data-Standby="{{ translate('serverBackups', 'standby', data['lang'])}}"
data-Failed="{{ translate('serverBackups', 'failed', data['lang'])}}"></button>
<button class="btn btn-outline-success backup-status" data-status="{{ backup.status }}" data-Standby="{{ translate('serverBackups', 'standby', data['lang'])}}" data-Failed="{{ translate('serverBackups', 'failed', data['lang'])}}"></button>
</div>
<br>
{% if backup.default %}
<span class="badge-pill badge-outline-warning">{{ translate('serverBackups', 'default',
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain"
data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i
class="fa-solid fa-question"></i></button></small>
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain" data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i class="fa-solid fa-question"></i></button></small>
{% end %}
</td>
<td id="backup_edit" class="action">
<button
onclick="window.location.href=`/panel/edit_backup?id={{ data['server_stats']['server_id']['server_id'] }}&backup_id={{backup.backup_id}}`"
class="btn btn-info">
<button onclick="window.location.href=`/panel/edit_backup?id={{ data['server_stats']['server_id']['server_id'] }}&backup_id={{backup.backup_id}}`" class="btn btn-info">
<i class="fas fa-pencil-alt"></i>
</button>
{% if not backup.default %}
@ -168,9 +148,7 @@
<i class="fas fa-trash" aria-hidden="true"></i>
</button>
{% end %}
<button data-backup={{ backup.backup_id }} data-toggle="tooltip"
title="{{ translate('serverBackups', 'run', data['lang']) }}"
class="btn btn-outline-warning test-socket backup_now_button">
<button data-backup={{ backup.backup_id }} data-toggle="tooltip" title="{{ translate('serverBackups', 'run', data['lang']) }}" class="btn btn-outline-warning test-socket backup_now_button">
<i class="fa-solid fa-forward"></i>
</button>
</td>
@ -193,47 +171,7 @@
</div>
<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;
}
</style>
<link rel="stylesheet" href="/static/assets/css/partial/crafty-backup.css">
<!-- content-wrapper ends -->
{% end %}
@ -266,8 +204,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
return;
@ -485,8 +423,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}
@ -510,8 +448,8 @@
\n<div id="${dpath}" data-path="${dpath}" data-name="${filename}" class="tree-caret tree-ctx-item tree-folder">
<input type="checkbox" class="checkBoxClass excluded" value="${dpath}" ${checked}>
<span id="${dpath}span" class="files-tree-title" data-path="${dpath}" data-name="${filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
<i class="text-info far fa-folder"></i>
<i class="text-info far fa-folder-open"></i>
<strong>${filename}</strong>
</span>
</input></div><li>`

View File

@ -43,12 +43,10 @@
<div class="col-md-6 col-sm-12">
<br>
<br>
<div id="{{data['backup_config'].get('backup_id', None)}}_status" class="progress"
style="height: 15px; display: none;">
<div id="{{data['backup_config'].get('backup_id', None)}}_status" class="progress" style="height: 15px; display: none;">
</div>
{% if data['backing_up'] %}
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
id="total_files">{{data['server_stats']['world_size']}}</span></p>
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span id="total_files">{{data['server_stats']['world_size']}}</span></p>
{% end %}
<br>
@ -63,43 +61,32 @@
<label for="backup_name">{{ translate('serverBackups', 'name', data['lang']) }}
{% if data["backup_config"].get("default", None) %}
&nbsp;&nbsp; <span class="badge-pill badge-outline-warning">{{ translate('serverBackups', 'default',
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain"
data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i
class="fa-solid fa-question"></i></button></small>
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain" data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i class="fa-solid fa-question"></i></button></small>
{% end %}
</label>
{% if data["backup_config"].get("backup_id", None) %}
<input type="text" class="form-control" name="backup_name" id="backup_name"
value="{{ data['backup_config']['backup_name'] }}">
<input type="text" class="form-control" name="backup_name" id="backup_name" value="{{ data['backup_config']['backup_name'] }}">
{% else %}
<input type="text" class="form-control" name="backup_name" id="backup_name"
placeholder="{{ translate('serverBackups', 'myBackup', data['lang']) }}">
<input type="text" class="form-control" name="backup_name" id="backup_name" placeholder="{{ translate('serverBackups', 'myBackup', data['lang']) }}">
{% end %}
<br>
<br>
{% if data['super_user'] %}
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang'])
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="backup_location" id="backup_location"
value="{{ data['backup_config']['backup_location'] }}"
placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}">
<input type="text" class="form-control" name="backup_location" id="backup_location" value="{{ data['backup_config']['backup_location'] }}" placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}">
{% end %}
</div>
<div class="form-group">
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang'])
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="max_backups" id="max_backups"
value="{{ data['backup_config']['max_backups'] }}"
placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
</div>
<div class="form-group">
<div class="custom-control custom-switch">
{% if data['backup_config']['compress'] %}
<input type="checkbox" class="custom-control-input" id="compress" name="compress" checked=""
value="True">
<input type="checkbox" class="custom-control-input" id="compress" name="compress" checked="" value="True">
{% else %}
<input type="checkbox" class="custom-control-input" id="compress" name="compress" value="True">
{% end %}
@ -110,8 +97,7 @@
<div class="form-group">
<div class="custom-control custom-switch">
{% if data['backup_config']['shutdown']%}
<input type="checkbox" class="custom-control-input" id="shutdown" name="shutdown" checked=""
value="True">
<input type="checkbox" class="custom-control-input" id="shutdown" name="shutdown" checked="" value="True">
{% else %}
<input type="checkbox" class="custom-control-input" id="shutdown" name="shutdown" value="True">
{% end %}
@ -123,13 +109,10 @@
<div class="custom-control custom-switch">
{% if data['backup_config']['before'] %}
<input type="checkbox" class="custom-control-input" id="before-check" name="before-check" checked>
<input type="text" class="form-control hidden-input" name="before" id="backup_before"
value="{{ data['backup_config']['before'] }}" placeholder="We enter the / for you"
style="display: inline-block;">
<input type="text" class="form-control hidden-input d-inline-block" name="before" id="backup_before" value="{{ data['backup_config']['before'] }}" placeholder="We enter the / for you">
{% else %}
<input type="checkbox" class="custom-control-input" id="before-check" name="before-check">
<input type="text" class="form-control hidden-input" name="before" id="backup_before" value=""
placeholder="We enter the / for you." style="display: none;">
<input type="text" class="form-control hidden-input" name="before" id="backup_before" value="" placeholder="We enter the / for you." style="display: none;">
{% end %}
<label for="before-check" class="custom-control-label">{{
translate('serverBackups', 'before', data['lang']) }}</label>
@ -139,14 +122,11 @@
<div class="custom-control custom-switch">
{% if data['backup_config']['after'] %}
<input type="checkbox" class="custom-control-input" id="after-check" name="after-check" checked>
<input type="text" class="form-control hidden-input" name="after" id="backup_after"
value="{{ data['backup_config']['after'] }}" placeholder="We enter the / for you"
style="display: inline-block;">
<input type="text" class="form-control hidden-input d-inline-block" name="after" id="backup_after" value="{{ data['backup_config']['after'] }}" placeholder="We enter the / for you">
<br>
{% else %}
<input type="checkbox" class="custom-control-input" id="after-check" name="after-check">
<input type="text" class="form-control hidden-input" name="after" id="backup_after" value=""
placeholder="We enter the / for you." style="display: none;">
<input type="text" class="form-control hidden-input" name="after" id="backup_after" value="" placeholder="We enter the / for you." style="display: none;">
{% end %}
<label for="after-check" class="custom-control-label">{{
translate('serverBackups', 'after', data['lang']) }}</label>
@ -156,8 +136,7 @@
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{
translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_files_button"
data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{
<button class="btn btn-primary mr-2" id="root_files_button" data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{
translate('serverBackups', 'clickExclude', data['lang']) }}</button>
</div>
<div class="modal fade" id="dir_select" tabindex="-1" aria-labelledby="dir_select" aria-hidden="true">
@ -171,8 +150,7 @@
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path=""
style="overflow: scroll; max-height:75%;">
<div class="tree-ctx-item overflow-scroll mh-75" id="main-tree-div" data-path="">
<input type="checkbox" id="main-tree-input" name="root_path" value="" disabled>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
@ -183,10 +161,8 @@
</div>
</div>
<div class="modal-footer">
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal"><i
class="fa-solid fa-xmark"></i></button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary"><i
class="fa-solid fa-thumbs-up"></i></button>
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal"><i class="fa-solid fa-xmark"></i></button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary"><i class="fa-solid fa-thumbs-up"></i></button>
</div>
</div>
</div>
@ -216,16 +192,13 @@
{% for backup in data['backup_list'] %}
<tr>
<td>
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}&backup_id={{ data['backup_config']['backup_id']}}"
class="btn btn-primary">
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}&backup_id={{ data['backup_config']['backup_id']}}" class="btn btn-primary">
<i class="fas fa-download" aria-hidden="true"></i>
{{ translate('serverBackups', 'download', data['lang']) }}
</a>
<br>
<br>
<button data-file="{{ backup['path'] }}"
data-backup_location="{{ data['backup_config']['backup_location'] }}"
class="btn btn-danger del_button">
<button data-file="{{ backup['path'] }}" data-backup_location="{{ data['backup_config']['backup_location'] }}" class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i>
{{ translate('serverBackups', 'delete', data['lang']) }}
</button>
@ -269,47 +242,7 @@
</div>
<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;
}
</style>
<link rel="stylesheet" href="/static/assets/css/partial/crafty-backup.css">
<!-- content-wrapper ends -->
{% end %}
@ -342,8 +275,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
return;
@ -676,8 +609,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}
@ -701,8 +634,8 @@
\n<div id="${dpath}" data-path="${dpath}" data-name="${filename}" class="tree-caret tree-ctx-item tree-folder">
<input type="checkbox" class="checkBoxClass excluded" value="${dpath}" ${checked}>
<span id="${dpath}span" class="files-tree-title" data-path="${dpath}" data-name="${filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
<i class="text-info far fa-folder"></i>
<i class="text-info far fa-folder-open"></i>
<strong>${filename}</strong>
</span>
</input></div><li>`

View File

@ -56,7 +56,7 @@
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
</label>
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<span style="color: gray; font-size: .9vw;">{{ data['server_stats']['server_id']['path'] }}</span>
<span class="text-secondary" style="font-size: .9vw;">{{ data['server_stats']['server_id']['path'] }}</span>
🔒
</div>
@ -100,7 +100,7 @@
{% else %}
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<span style="color: gray; font-size: .9vw;">{{ data['server_stats']['server_id']['execution_command'] }}</span> 🔒
<span class="text-secondary" style="font-size: .9vw;">{{ data['server_stats']['server_id']['execution_command'] }}</span> 🔒
</div>
<br>
{% end %}

File diff suppressed because it is too large Load Diff

View File

@ -41,8 +41,7 @@
<div class="col-md-12">
<div class="input-group">
<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 id="virt_console" class="virt_console">
</div>
</div>
<br />
@ -51,12 +50,9 @@
<br />
<br />
<h4>{{ translate('serverDetails', 'filterList', data['lang']) }}</h4>
<ul id="ignored-words" style="list-style: None;"></ul>
<ul id="ignored-words" class="list-group"></ul>
<br />
</div>
</div>
</div>
</div>
@ -148,8 +144,8 @@
// Display filtered word
words.push(word);
$("#ignored-words").append(
`<li id=${safe_word.replaceAll(" ", "-")}>` +
"<div class='card-header header-sm d-flex justify-content-between align-items-center'>" +
`<li id=${safe_word.replaceAll(" ", "-")} class="list-group-item">` +
"<div class='text-sm d-flex justify-content-between align-items-center'>" +
`${word}&nbsp;<button class='btn btn-danger' onclick='deleteWord("${word}")' >` +
"<i class='fas fa-trash'></i></button></div></li>"
);
@ -163,8 +159,8 @@
let safe_word = sanitize(word);
$("#ignored-words").append(
`<li id=${safe_word.replaceAll(" ", "-")}>` +
"<div class='card-header header-sm d-flex justify-content-between align-items-center'>" +
`<li id=${safe_word.replaceAll(" ", "-")} class="list-group-item">` +
"<div class='text-sm d-flex justify-content-between align-items-center'>" +
`${word}&nbsp;<button class='btn btn-danger' onclick='deleteWord("${word}")' >` +
"<i class='fas fa-trash'></i></button></div></li>"
);
@ -212,8 +208,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}

View File

@ -48,12 +48,10 @@
</select>
</div>
</div>
<button style="float: right; visibility: hidden;" class="btn btn-outline-success reset-button"
id="reset-button"><i class="fas fa-undo"></i>&nbsp;{{ translate('serverMetrics', 'resetZoom', data['lang'])
<button class="btn btn-outline-success reset-button float-right d-none" id="reset-button" hidden><i class="fas fa-undo"></i>&nbsp;{{ translate('serverMetrics', 'resetZoom', data['lang'])
}}</button>
{% if data['user_data']['hints'] %}
<span data-html="true" class="hints text-center" title="<i class='fa fa-info-circle'></i> " ,
data-content="{{
<span data-html="true" class="hints text-center" title="<i class='fa fa-info-circle'></i> " , data-content="{{
translate('serverMetrics', 'zoomHint1' , data['lang'])}} <br> <br> {{ translate('serverMetrics', 'zoomHint2', data['lang'])}}" , data-placement="top"></span>
{% end %}
<div class="chart-wrapper">
@ -72,11 +70,6 @@
.chart-wrapper {
height: 65vh;
}
.popover-body {
color: white !important;
;
}
</style>
<script type="text/javascript">
const serverId = new URLSearchParams(document.location.search).get('id')
@ -135,7 +128,8 @@
zoom: {
onZoom({ hist_chart }) {
console.log("zooming");
document.getElementById("reset-button").style.visibility = "visible";
document.getElementById("reset-button").classList.remove("d-none");
document.getElementById("reset-button").classList.add("d-block");
zoomed = true;
},
wheel: {
@ -232,7 +226,8 @@
console.log("resetting zoom");
zoomed = false;
hist_chart.resetZoom();
document.getElementById("reset-button").style.visibility = "hidden";
document.getElementById("reset-button").classList.add("d-none");
document.getElementById("reset-button").classList.remove("d-block");
});
});
</script>

View File

@ -37,24 +37,19 @@
<div class="row">
<div class="col-md-8 col-sm-8">
{% if data['new_schedule'] == True %}
<form class="forms-sample" method="post" id="new_schedule_form"
action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
<form class="forms-sample" method="post" id="new_schedule_form" action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
{% else %}
<form class="forms-sample" method="post" id="schedule_form"
action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
<form class="forms-sample" method="post" id="schedule_form" action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
{% end %}
<div class="form-group">
<label for="name">{{ translate('serverSchedules', 'name' , data['lang']) }}</label>
<input type="input" class="form-control" name="name" id="name_input"
value="{{ data['schedule']['name']}}" maxlength="30" placeholder="Name" required>
<input type="input" class="form-control" name="name" id="name_input" value="{{ data['schedule']['name']}}" maxlength="30" placeholder="Name" required>
</div>
<div class="form-group">
<label for="difficulty">{{ translate('serverScheduleConfig', 'select' , data['lang']) }}<small
class="text-muted ml-1"></small> </label><br>
<select id="difficulty" name="difficulty" onchange="basicAdvanced(this);"
class="form-control form-control-lg select-css" value="{{ data['schedule']['difficulty'] }}">
<label for="difficulty">{{ translate('serverScheduleConfig', 'select' , data['lang']) }}<small class="text-muted ml-1"></small> </label><br>
<select id="difficulty" name="difficulty" onchange="basicAdvanced(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['difficulty'] }}">
<option id="basic" value="basic">{{ translate('serverScheduleConfig', 'basic' , data['lang']) }}
</option>
<option id="advanced" value="advanced">{{ translate('serverScheduleConfig', 'cron' , data['lang'])
@ -64,10 +59,8 @@
</select>
</div>
<div class="form-group">
<label for="server_name">{{ translate('serverSchedules', 'action' , data['lang']) }}<small
class="text-muted ml-1"></small> </label><br>
<select id="action" name="action" onchange="yesnoCheck(this);"
class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
<label for="server_name">{{ translate('serverSchedules', 'action' , data['lang']) }}<small class="text-muted ml-1"></small> </label><br>
<select id="action" name="action" onchange="yesnoCheck(this);" class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
<option id="start" value="start">{{ translate('serverScheduleConfig', 'start' , data['lang']) }}
</option>
<option id="restart" value="restart">{{ translate('serverScheduleConfig', 'restart' ,
@ -79,9 +72,9 @@
<option id="command" value="command">{{ translate('serverScheduleConfig', 'custom' , data['lang'])
}}</option>
</select>
<div id="ifBackup" style="display: none;">
<div id="ifBackup" class="d-none">
<br>
<label for="action_id">{{ translate('serverSchedules', 'actionId' , data['lang']) }}<small
<label id="action_id_label" for="action_id" data-backup-translate="{{ translate('serverSchedules', 'backupPol', data['lang']) }}" data-action-translate="{{ translate('serverSchedules', 'actionId', data['lang']) }}"><small
class="text-muted ml-1"></small> </label><br>
<select id="action_id" name="action_id"
class="form-control form-control-lg select-css" value="{{ data['schedule']['action_id'] }}">
@ -100,16 +93,12 @@
</div>
<div id="ifBasic">
<div class="form-group">
<label for="server_path">{{ translate('serverScheduleConfig', 'interval' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' ,
<label for="server_path">{{ translate('serverScheduleConfig', 'interval' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'interval-explain' ,
data['lang']) }}</small> </label>
<input type="number" class="form-control" name="interval" id="interval"
value="{{ data['schedule']['interval'] }}" placeholder="Interval" required min="1">
<input type="number" class="form-control" name="interval" id="interval" value="{{ data['schedule']['interval'] }}" placeholder="Interval" required min="1">
<br>
<br>
<select id="interval_type" onchange="ifDays(this);" name="interval_type"
class="form-control form-control-lg select-css"
value="{{ data['schedule']['interval_type'] }}">
<select id="interval_type" onchange="ifDays(this);" name="interval_type" class="form-control form-control-lg select-css" value="{{ data['schedule']['interval_type'] }}">
<option id="days" value="days">{{ translate('serverScheduleConfig', 'days' , data['lang']) }}
</option>
<option id="hours" value="hours">{{ translate('serverScheduleConfig', 'hours' , data['lang']) }}
@ -118,48 +107,38 @@
data['lang']) }}</option>
</select>
</div>
<div id="ifDays" style="display: block;">
<div id="ifDays" class="d-block">
<div class="form-group">
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' ,
<label for="time">{{ translate('serverScheduleConfig', 'time' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'time-explain' ,
data['lang']) }}</small> </label>
<input type="time" class="form-control" name="start_time" id="time"
value="{{ data['schedule']['time'] }}" placeholder="Time" required>
<input type="time" class="form-control" name="start_time" id="time" value="{{ data['schedule']['time'] }}" placeholder="Time" required>
</div>
</div>
</div>
<div id="ifYes" style="display: none;">
<div id="ifYes" class="d-none">
<div class="form-group">
<label for="command">{{ translate('serverScheduleConfig', 'command' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'command-explain' ,
<label for="command">{{ translate('serverScheduleConfig', 'command' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'command-explain' ,
data['lang']) }}</small> </label>
<input type="input" class="form-control" name="command" id="command_input"
value="{{ data['schedule']['command'] }}" placeholder="Command" required>
<input type="input" class="form-control" name="command" id="command_input" value="{{ data['schedule']['command'] }}" placeholder="Command" required>
</div>
</div>
<div id="ifAdvanced" style="display: none;">
<div id="ifAdvanced" class="d-none">
<div class="form-group">
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang'])
<label for="cron">{{ translate('serverScheduleConfig', 'cron' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'cron-explain' , data['lang'])
}}</small> </label>
<input type="input" class="form-control" name="cron_string" id="cron"
value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
<input type="input" class="form-control" name="cron_string" id="cron" value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
</div>
</div>
<div id="ifReaction" style="display: none;">
<div id="ifReaction" class="d-none">
<div class="form-group">
<label for="delay">{{ translate('serverScheduleConfig', 'offset' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'offset-explain' ,
<label for="delay">{{ translate('serverScheduleConfig', 'offset' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'offset-explain' ,
data['lang']) }}</small> </label>
<input type="number" class="form-control" name="delay" id="delay"
value="{{ data['schedule']['delay']}}">
<input type="number" class="form-control" name="delay" id="delay" value="{{ data['schedule']['delay']}}">
<br>
<br>
<label for="parent">{{ translate('serverScheduleConfig', 'parent' , data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'parent-explain' ,
<label for="parent">{{ translate('serverScheduleConfig', 'parent' , data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverScheduleConfig', 'parent-explain' ,
data['lang']) }}</small> </label>
<select id="parent" name="parent" class="form-control form-control-lg select-css"
value="{{ data['schedule']['action'] }}">
<select id="parent" name="parent" class="form-control form-control-lg select-css" value="{{ data['schedule']['action'] }}">
{% if data['parent'] %}
<option id="{{data['parent']['schedule_id']}}" value="{{data['parent']['schedule_id']}}">
{{data['parent']['name']}} | {{data['parent']['command']}} | {{data['parent']['interval']}}
@ -196,8 +175,7 @@
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked=""
value="1">{{ translate('serverScheduleConfig', 'enabled' , data['lang']) }}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">{{ translate('serverScheduleConfig', 'enabled' , data['lang']) }}
</label>
</div>
@ -210,19 +188,17 @@
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{
translate('serverConfig', 'save', data['lang']) }}</button>
<button type="reset"
onclick="location.href=`/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules`"
class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel',
<button type="reset" onclick="location.href=`/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules`" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel',
data['lang']) }}</button>
</form>
</div>
<div class="col-sm-4 grid-margin">
<h4>{{ translate('serverScheduleConfig', 'children' , data['lang']) }}</h4>
<ul>
<ul class="list-group">
{% for schedule in data['schedule']['children'] %}
<li style="overflow: auto;"><a
href="/panel/edit_schedule?id={{schedule.server_id}}&sch_id={{schedule.schedule_id}}">{{schedule.name}}
| {{schedule.schedule_id}}</a></li>
<li class="list-group-item">
<a href="/panel/edit_schedule?id={{schedule.server_id}}&sch_id={{schedule.schedule_id}}">{{schedule.name}} | {{schedule.schedule_id}}</a>
</li>
{% end %}
</ul>
</div>
@ -318,8 +294,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
});
@ -375,57 +351,79 @@
function yesnoCheck() {
if (document.getElementById('action').value == "command") {
document.getElementById("ifYes").style.display = "block";
document.getElementById("ifYes").classList.add("d-block");
document.getElementById("ifYes").classList.remove("d-none");
document.getElementById("command_input").required = true;
} else {
document.getElementById("ifYes").style.display = "none";
document.getElementById("ifYes").classList.add("d-none");
document.getElementById("ifYes").classList.remove("d-block");
document.getElementById("command_input").required = false;
}
if (document.getElementById('action').value == "backup") {
document.getElementById("ifBackup").style.display = "block";
document.getElementById("ifBackup").classList.add("d-block");
document.getElementById("ifBackup").classList.remove("d-none");
document.getElementById("action_id").required = true;
$("#action_id_label").html($("#action_id_label").attr("data-backup-translate"));
} else {
document.getElementById("ifBackup").style.display = "none";
document.getElementById("ifBackup").classList.remove("d-block");
document.getElementById("ifBackup").classList.add("d-none");
document.getElementById("action_id").required = false;
$("#action_id").val(null);
$("#action_id_label").html($("#action_id_label").attr("data-action-translate"));
}
}
function basicAdvanced() {
if (document.getElementById('difficulty').value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifReaction").style.display = "none";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("ifAdvanced").classList.add("d-block");
document.getElementById("ifReaction").classList.add("d-none");
document.getElementById("ifBasic").classList.add("d-none");
document.getElementById("ifAdvanced").classList.remove("d-none");
document.getElementById("ifReaction").classList.remove("d-block");
document.getElementById("ifBasic").classList.remove("d-block");
document.getElementById("delay").required = false;
document.getElementById("parent").required = false;
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
document.getElementById("ifDays").classList.add("d-none");
document.getElementById("ifDays").classList.remove("d-block");
} else if (document.getElementById('difficulty').value == "reaction") {
document.getElementById("ifReaction").style.display = "block";
document.getElementById("ifBasic").style.display = "none";
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifReaction").classList.add("d-block");
document.getElementById("ifBasic").classList.add("d-none");
document.getElementById("ifAdvanced").classList.add("d-none");
document.getElementById("ifReaction").classList.remove("d-none");
document.getElementById("ifBasic").classList.remove("d-block");
document.getElementById("ifAdvanced").classList.remove("d-block");
document.getElementById("delay").required = true;
document.getElementById("parent").required = true;
document.getElementById("interval").required = false;
document.getElementById("time").required = false;
document.getElementById("ifDays").classList.add("d-none");
document.getElementById("ifDays").classList.remove("d-block");
$("#cron").val("");
}
else {
document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifReaction").style.display = "none";
document.getElementById("ifBasic").style.display = "block";
document.getElementById("ifAdvanced").classList.add("d-none");
document.getElementById("ifReaction").classList.add("d-none");
document.getElementById("ifAdvanced").classList.remove("d-block");
document.getElementById("ifReaction").classList.remove("d-block");
document.getElementById("ifBasic").classList.add("d-block");
document.getElementById("ifBasic").classList.remove("d-none");
document.getElementById("delay").required = false;
document.getElementById("parent").required = false;
document.getElementById("interval").required = true;
document.getElementById("time").required = true;
ifDays();
$("#cron").val("");
}
}
function ifDays() {
if (document.getElementById('interval_type').value == "days") {
document.getElementById("ifDays").style.display = "block";
document.getElementById("ifDays").classList.add("d-block");
document.getElementById("ifDays").classList.remove("d-none");
document.getElementById("time").required = true;
} else {
document.getElementById("ifDays").style.display = "none";
document.getElementById("ifDays").classList.add("d-none");
document.getElementById("ifDays").classList.remove("d-block");
document.getElementById("time").required = false;
}
}

View File

@ -41,7 +41,7 @@
</span>
<div class="row">
<div class="col-md-12 col-sm-12" style="overflow-x:auto;">
<div class="col-md-12 col-sm-12 overflow-x-auto">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-calendar"></i> {{ translate('serverSchedules',
@ -53,13 +53,13 @@
</div>
<div class="card-body">
{% if len(data['schedules']) == 0 %}
<div style="text-align: center; color: grey;">
<div class="text-align-center text-secondary">
<h7>{{ translate('serverSchedules', 'no-schedule', data['lang']) }} <strong>{{ translate('serverSchedules', 'newSchedule',data['lang']) }}</strong>.</h7>
</div>
{% end %}
{% if len(data['schedules']) > 0 %}
<div class="d-none d-lg-block">
<table class="table table-hover table-responsive" id="schedule_table" style="table-layout:fixed;" aria-describedby="Schedule List">
<table class="table table-hover table-responsive no-scroll" id="schedule_table" style="table-layout:fixed;" aria-describedby="Schedule List">
<thead>
<tr class="rounded">
<th style="width: 5%; min-width: 64px;">{{ translate('serverSchedules', 'enabled',
@ -92,8 +92,8 @@
<td id="{{schedule.action}}">
<p>{{schedule.action}}</p>
</td>
<td id="{{schedule.command}}" style="overflow: scroll; max-width: 30px;">
<p style="overflow: scroll;" class="no-scroll">{{schedule.command}}</p>
<td id="{{schedule.command}}" class="overflow-scroll" style="max-width: 30px;">
<p class="no-scroll">{{schedule.command}}</p>
</td>
<td id="{{schedule.interval}}">
{% if schedule.interval_type != '' and schedule.interval_type != 'reaction' %}
@ -142,8 +142,8 @@
<td id="{{schedule.action}}" class="action">
<p>{{schedule.action}}</p>
</td>
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
<p style="overflow: scroll;">{{schedule.command}}</p>
<td id="{{schedule.command}}" class="action overflow-scroll" style="max-width: 30px;">
<p class="overflow-scroll;">{{schedule.command}}</p>
</td>
<td id="{{schedule.enabled}}" class="enabled">
<button type="button" class="btn btn-sm btn-info btn-toggle schedule-custom-toggle" id="schedule{{schedule.id}}" name="schedule{{schedule.id}}" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}" data-toggle="button" aria-pressed="true" autocomplete="off">
@ -163,43 +163,42 @@
</button>
</div>
<div class="modal-body">
<ul style="list-style: none;">
<li id="{{schedule.schedule_id}}" class="id" style="border-top: .1em solid gray;">
<ul class="list-group">
<li id="{{schedule.schedule_id}}" class="id list-group-item">
<h4>{{ translate('serverSchedules', 'name', data['lang']) }}</h4>
<p>{{schedule.schedule_id}}</p>
<p class="mb-0">{{schedule.schedule_id}}</p>
</li>
<li id="{{schedule.action}}" class="action" style="border-top: .1em solid gray;">
<li id="{{schedule.action}}" class="action list-group-item">
<h4>{{ translate('serverSchedules', 'action', data['lang']) }}</h4>
<p>{{schedule.action}}</p>
<p class="mb-0">{{schedule.action}}</p>
</li>
<li id="{{schedule.command}}" class="action" style="border-top: .1em solid gray;">
<li id="{{schedule.command}}" class="action list-group-item">
<h4>{{ translate('serverSchedules', 'command', data['lang']) }}</h4>
<p>{{schedule.command}}</p>
<p class="mb-0">{{schedule.command}}</p>
</li>
<li id="{{schedule.interval}}" class="action" style="border-top: .1em solid gray;">
<li id="{{schedule.interval}}" class="action list-group-item">
{% if schedule.interval != '' %}
<h4>{{ translate('serverSchedules', 'interval', data['lang']) }}</h4>
<p>{{ translate('serverSchedules', 'every', data['lang']) }} {{schedule.interval}}
<p class="mb-0">{{ translate('serverSchedules', 'every', data['lang']) }} {{schedule.interval}}
{{schedule.interval_type}}</p>
{% elif schedule.interval_type == 'reaction' %}
<h4>{{ translate('serverSchedules', 'interval', data['lang']) }}</h4>
<p>{{schedule.interval_type}}<br><br>{{ translate('serverSchedules', 'child',
<p class="mb-0">{{schedule.interval_type}}<br><br>{{ translate('serverSchedules', 'child',
data['lang']) }}: {{ schedule.parent }}</p>
{% else %}
<h4>{{ translate('serverSchedules', 'interval', data['lang']) }}</h4>
<p>{{ translate('serverSchedules', 'cron', data['lang']) }}: {{schedule.cron_string}}
</p>
<p class="mb-0">{{ translate('serverSchedules', 'cron', data['lang']) }}: {{schedule.cron_string}}</p>
{% end %}
</li>
<li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;">
<li id="{{schedule.start_time}}" class="action list-group-item">
<h4>{{ translate('serverSchedules', 'nextRun', data['lang']) }}</h4>
{% if schedule.next_run %}
<p>{{schedule.next_run}}</p>
<p class="mb-0">{{schedule.next_run}}</p>
{% else %}
<p>zzzzzzz</p>
<p class="mb-0">zzzzzzz</p>
{% end %}
</li>
<li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
<li id="{{schedule.enabled}}" class="action list-group-item">
<h4>{{ translate('serverSchedules', 'enabled', data['lang']) }}</h4>
<input type="checkbox" class="schedule-enabled-toggle" data-schedule-id="{{schedule.schedule_id}}" data-schedule-enabled="{{ 'true' if schedule.enabled else 'false' }}">
</li>
@ -235,11 +234,6 @@
</div>
</div>
<style>
.popover-body {
color: white !important;
;
}
.toggle-handle {
background-color: white !important;
}

View File

@ -41,96 +41,66 @@
</span>
<div class="col-md-12">
<button id="to-bottom" style="visibility: hidden; float: right;" class="btn btn-outline-success" hidden>{{
translate('serverDetails', 'reset', data['lang']) }}</button>
<button id="to-bottom" class="btn btn-outline-success" hidden>
{{ translate('serverDetails', 'reset', data['lang']) }}
</button>
<div class="input-group">
<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 id="virt_console">
</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="">
<div class="input-group flex-wrap server_command">
<input type="text" class="form-control server_command" 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>
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand', data['lang']) }}</button>
</span>
</div>
{% if data['permissions']['Commands'] in data['user_permissions'] %}
{% if data['importing'] and data['server_stats']['running']%}
<div id="update_control_buttons"
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled"><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{translate('serverTerm', 'installing', data['lang'])
<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">
<button onclick="" id="start-btn" class="btn btn-warning m-1 flex-grow-1 term-btn disabled"><i class="fa fa-spinner fa-spin"></i>&nbsp;{{translate('serverTerm', 'installing', data['lang'])
}}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
<button onclick="" id="restart-btn" class="btn btn-outline-primary m-1 flex-grow-1 term-btn 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 onclick="" id="stop-btn" class="btn btn-danger m-1 flex-grow-1 term-btn disabled">{{ translate('serverTerm', 'stop', data['lang'])
}}</button>
</div>
{% elif data['server_stats']['updating']%}
<div id="update_control_buttons"
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i>&nbsp;{{
<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">
<button onclick="" id="start-btn" class="btn btn-warning m-1 flex-grow-1 term-btn disabled"><i class="fa fa-spinner fa-spin"></i>&nbsp;{{
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',
<button onclick="" id="restart-btn" class="btn btn-outline-primary m-1 flex-grow-1 term-btn 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 onclick="" id="stop-btn" class="btn btn-danger m-1 flex-grow-1 term-btn 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',
<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">
<button onclick="" id="start-btn" class="btn btn-secondary m-1 flex-grow-1 term-btn 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',
<button onclick="" id="restart-btn" class="btn btn-outline-primary m-1 flex-grow-1 term-btn 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 onclick="" id="stop-btn" class="btn btn-danger m-1 flex-grow-1 term-btn 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> {{
<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">
<button onclick="" id="start-btn" class="btn btn-secondary m-1 flex-grow-1 term-btn 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',
<button onclick="" id="restart-btn" class="btn btn-outline-primary m-1 flex-grow-1 term-btn 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 onclick="" id="stop-btn" class="btn btn-danger m-1 flex-grow-1 term-btn 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'])
<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">
<button onclick="send_command(serverId, 'start_server');" id="start-btn" class="btn btn-primary m-1 flex-grow-1 term-btn">{{ translate('serverTerm', 'start', data['lang']) }}</button>
<button onclick="send_command(serverId, 'restart_server');" id="restart-btn" class="btn btn-outline-primary m-1 flex-grow-1 term-btn">{% 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>
<button onclick="send_command(serverId, 'stop_server');" id="stop-btn" class="btn btn-danger m-1 flex-grow-1 term-btn">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>
{% end %}
{% end %}
@ -140,21 +110,7 @@
</div>
</div>
</div>
<style>
#virt_console::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
#virt_console {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
white-space: pre-wrap;
}
</style>
<link rel="stylesheet" href="/static/assets/css/partial/crafty-terminal.css">
<!-- content-wrapper ends -->
{% end %}
@ -189,8 +145,8 @@
console.log("Command received successfully")
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}
@ -200,12 +156,12 @@
if (updateButton.isUpdating) {
if (updateButton.server_id == serverId) {
console.log(updateButton.isUpdating)
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ 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">{% 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>';
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" class="btn btn-primary m-1 flex-grow-1 term-btn">{{ translate("serverTerm", "updating", data["lang"]) }}</button><button onclick="" id="restart-btn" class="btn btn-outline-primary m-1 flex-grow-1 term-btn">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" class="btn btn-danger m-1 flex-grow-1 term-btn disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>';
}
}
else if (updateButton.server_id == serverId) {
window.location.reload()
document.getElementById('update_control_buttons').innerHTML = '<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="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>';
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(serverId, "start_server");" id="start-btn" class="btn btn-primary m-1 flex-grow-1 term-btn">{{ translate("serverTerm", "start", data["lang"]) }}</button><button onclick="send_command(serverId, "restart_server");" id="restart-btn" class="btn btn-outline-primary m-1 flex-grow-1 term-btn">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" class="btn btn-danger m-1 flex-grow-1 term-btn disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>';
}
});
}
@ -256,8 +212,8 @@
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
title: responseData.error,
message: responseData.error_data
});
}
}

Some files were not shown because too many files have changed in this diff Show More