mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into 'master'
Alpha 3.5 See merge request crafty-controller/crafty-commander!150
This commit is contained in:
commit
9db35445bc
@ -1,8 +1,19 @@
|
||||
# docker related
|
||||
docker/
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
|
||||
# git & gitlab related
|
||||
.git/
|
||||
.gitignore
|
||||
.gitlab-ci.yml
|
||||
|
||||
# root
|
||||
.editorconfig
|
||||
.pylintrc
|
||||
.venv
|
||||
.vscode
|
||||
crafty_commander.exe
|
||||
DBCHANGES.md
|
||||
docker-compose.yml.example
|
||||
|
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*.{js,py,html}]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
# end_of_line = lf
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{js,html}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
@ -1,15 +1,47 @@
|
||||
stages:
|
||||
- win-dev
|
||||
- win-prod
|
||||
- docker-dev
|
||||
- docker-prod
|
||||
- test
|
||||
- prod-deployment
|
||||
- dev-deployment
|
||||
|
||||
variables:
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
|
||||
pylint:
|
||||
stage: test
|
||||
image: python:3.7-slim
|
||||
services:
|
||||
- name: docker:dind
|
||||
tags:
|
||||
- 'docker_testers'
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
|
||||
when: never
|
||||
before_script:
|
||||
- mkdir -p public/badges public/lint
|
||||
- echo undefined > public/badges/$CI_JOB_NAME.score
|
||||
- pip install pylint-gitlab
|
||||
script:
|
||||
- pylint --exit-zero --output-format=text $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**") | tee /tmp/pylint.txt
|
||||
- sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' /tmp/pylint.txt > public/badges/$CI_JOB_NAME.score
|
||||
- pylint --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**") > codeclimate.json
|
||||
after_script:
|
||||
- anybadge --overwrite --label $CI_JOB_NAME --value=$(cat public/badges/$CI_JOB_NAME.score) --file=public/badges/$CI_JOB_NAME.svg 4=red 6=orange 8=yellow 10=green
|
||||
- |
|
||||
echo "Your score is: $(cat public/badges/$CI_JOB_NAME.score)"
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
reports:
|
||||
codequality: codeclimate.json
|
||||
when: always
|
||||
|
||||
docker-build-dev:
|
||||
image: docker:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--experimental"]
|
||||
stage: docker-dev
|
||||
stage: dev-deployment
|
||||
tags:
|
||||
- docker
|
||||
rules:
|
||||
@ -26,13 +58,15 @@ docker-build-dev:
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
|
||||
docker version
|
||||
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
tag=":$CI_COMMIT_REF_SLUG"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
- docker buildx create --use --name zedBuilder
|
||||
- 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
|
||||
@ -42,6 +76,7 @@ docker-build-dev:
|
||||
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"
|
||||
|
||||
@ -49,8 +84,7 @@ docker-build-prod:
|
||||
image: docker:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--experimental"]
|
||||
stage: docker-prod
|
||||
stage: prod-deployment
|
||||
tags:
|
||||
- docker
|
||||
rules:
|
||||
@ -67,13 +101,15 @@ docker-build-prod:
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
|
||||
docker version
|
||||
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- docker run --rm --privileged aptman/qus -- -r
|
||||
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
tag=""
|
||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||
- docker buildx create --use --name zedBuilder
|
||||
- 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
|
||||
@ -83,11 +119,12 @@ docker-build-prod:
|
||||
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}"
|
||||
|
||||
win-dev-build:
|
||||
stage: win-dev
|
||||
stage: dev-deployment
|
||||
tags:
|
||||
- win64
|
||||
cache:
|
||||
@ -111,7 +148,14 @@ win-dev-build:
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
--hidden-import apscheduler
|
||||
--collect-all tzlocal
|
||||
--collect-all tzdata
|
||||
--collect-all pytz
|
||||
--collect-all six
|
||||
|
||||
artifacts:
|
||||
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
@ -119,9 +163,9 @@ win-dev-build:
|
||||
- app\classes\**\*
|
||||
# Download latest:
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build
|
||||
|
||||
|
||||
win-prod-build:
|
||||
stage: win-prod
|
||||
stage: prod-deployment
|
||||
tags:
|
||||
- win64
|
||||
cache:
|
||||
@ -145,11 +189,18 @@ win-prod-build:
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
--hidden-import apscheduler
|
||||
--collect-all tzlocal
|
||||
--collect-all tzdata
|
||||
--collect-all pytz
|
||||
--collect-all six
|
||||
|
||||
artifacts:
|
||||
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
exclude:
|
||||
- app\classes\**\*
|
||||
# Download latest:
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build
|
||||
|
603
.pylintrc
Normal file
603
.pylintrc
Normal file
@ -0,0 +1,603 @@
|
||||
[MASTER]
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code.
|
||||
extension-pkg-allow-list=
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
|
||||
# for backward compatibility.)
|
||||
extension-pkg-whitelist=
|
||||
|
||||
# Return non-zero exit code if any of these messages/categories are detected,
|
||||
# even if score is above --fail-under value. Syntax same as enable. Messages
|
||||
# specified are enabled, while categories only check already-enabled messages.
|
||||
fail-on=
|
||||
|
||||
# Specify a score threshold to be exceeded before program exits with error.
|
||||
fail-under=10.0
|
||||
|
||||
# Files or directories to be skipped. They should be base names, not paths.
|
||||
ignore=
|
||||
|
||||
# Add files or directories matching the regex patterns to the ignore-list. The
|
||||
# regex matches against paths and can be in Posix or Windows format.
|
||||
ignore-paths=app/migrations, app/classes/shared/migration.py
|
||||
|
||||
# Files or directories matching the regex patterns are skipped. The regex
|
||||
# matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
|
||||
# number of processors available to use.
|
||||
jobs=0
|
||||
|
||||
# Control the amount of potential inferred values when inferring a single
|
||||
# object. This can help the performance when dealing with large functions or
|
||||
# complex, nested conditions.
|
||||
limit-inference-results=100
|
||||
|
||||
# List of plugins (as comma separated values of python module names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Minimum Python version to use for version dependent checks. Will default to
|
||||
# the version used to run pylint.
|
||||
py-version=3.9
|
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||
# user-friendly hints instead of false-positive error messages.
|
||||
suggestion-mode=yes
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
|
||||
confidence=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once). You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use "--disable=all --enable=classes
|
||||
# --disable=W".
|
||||
disable=abstract-method,
|
||||
attribute-defined-outside-init,
|
||||
bad-inline-option,
|
||||
bare-except,
|
||||
broad-except,
|
||||
cell-var-from-loop,
|
||||
consider-iterating-dictionary,
|
||||
consider-using-with,
|
||||
deprecated-pragma,
|
||||
duplicate-code,
|
||||
file-ignored,
|
||||
fixme,
|
||||
import-error,
|
||||
inconsistent-return-statements,
|
||||
invalid-name,
|
||||
locally-disabled,
|
||||
logging-format-interpolation,
|
||||
logging-fstring-interpolation,
|
||||
logging-not-lazy,
|
||||
missing-docstring,
|
||||
no-else-break,
|
||||
no-else-continue,
|
||||
no-else-return,
|
||||
no-self-use,
|
||||
no-value-for-parameter,
|
||||
not-an-iterable,
|
||||
protected-access,
|
||||
simplifiable-condition,
|
||||
simplifiable-if-statement,
|
||||
suppressed-message,
|
||||
too-few-public-methods,
|
||||
too-many-arguments,
|
||||
too-many-branches,
|
||||
too-many-instance-attributes,
|
||||
too-many-locals,
|
||||
too-many-nested-blocks,
|
||||
too-many-public-methods,
|
||||
too-many-return-statements,
|
||||
too-many-statements,
|
||||
use-symbolic-message-instead,
|
||||
useless-suppression,
|
||||
raw-checker-failed
|
||||
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
enable=c-extension-no-member
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Python expression which should return a score less than or equal to 10. You
|
||||
# have access to the variables 'error', 'warning', 'refactor', and 'convention'
|
||||
# which contain the number of messages in each category, as well as 'statement'
|
||||
# which is the total number of statements analyzed. This score is used by the
|
||||
# global evaluation report (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details.
|
||||
#msg-template=
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, json
|
||||
# and msvs (visual studio). You can also give a reporter class, e.g.
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Tells whether to display a full report or only the messages.
|
||||
reports=no
|
||||
|
||||
# Activate the evaluation score.
|
||||
score=yes
|
||||
|
||||
|
||||
[REFACTORING]
|
||||
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
# Complete name of functions that never returns. When checking for
|
||||
# inconsistent-return-statements if a never returning function is called then
|
||||
# it will be considered as an explicit return statement and no message will be
|
||||
# printed.
|
||||
never-returning-functions=sys.exit,argparse.parse_error
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Naming style matching correct argument names.
|
||||
argument-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct argument names. Overrides argument-
|
||||
# naming-style.
|
||||
#argument-rgx=
|
||||
|
||||
# Naming style matching correct attribute names.
|
||||
attr-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct attribute names. Overrides attr-naming-
|
||||
# style.
|
||||
#attr-rgx=
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma.
|
||||
bad-names=foo,
|
||||
bar,
|
||||
baz,
|
||||
toto,
|
||||
tutu,
|
||||
tata
|
||||
|
||||
# Bad variable names regexes, separated by a comma. If names match any regex,
|
||||
# they will always be refused
|
||||
bad-names-rgxs=
|
||||
|
||||
# Naming style matching correct class attribute names.
|
||||
class-attribute-naming-style=any
|
||||
|
||||
# Regular expression matching correct class attribute names. Overrides class-
|
||||
# attribute-naming-style.
|
||||
#class-attribute-rgx=
|
||||
|
||||
# Naming style matching correct class constant names.
|
||||
class-const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct class constant names. Overrides class-
|
||||
# const-naming-style.
|
||||
#class-const-rgx=
|
||||
|
||||
# Naming style matching correct class names.
|
||||
class-naming-style=PascalCase
|
||||
|
||||
# Regular expression matching correct class names. Overrides class-naming-
|
||||
# style.
|
||||
#class-rgx=
|
||||
|
||||
# Naming style matching correct constant names.
|
||||
const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct constant names. Overrides const-naming-
|
||||
# style.
|
||||
#const-rgx=
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
# Naming style matching correct function names.
|
||||
function-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct function names. Overrides function-
|
||||
# naming-style.
|
||||
#function-rgx=
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma.
|
||||
good-names=i,
|
||||
j,
|
||||
k,
|
||||
ex,
|
||||
Run,
|
||||
_
|
||||
|
||||
# Good variable names regexes, separated by a comma. If names match any regex,
|
||||
# they will always be accepted
|
||||
good-names-rgxs=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name.
|
||||
include-naming-hint=no
|
||||
|
||||
# Naming style matching correct inline iteration names.
|
||||
inlinevar-naming-style=any
|
||||
|
||||
# Regular expression matching correct inline iteration names. Overrides
|
||||
# inlinevar-naming-style.
|
||||
#inlinevar-rgx=
|
||||
|
||||
# Naming style matching correct method names.
|
||||
method-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct method names. Overrides method-naming-
|
||||
# style.
|
||||
#method-rgx=
|
||||
|
||||
# Naming style matching correct module names.
|
||||
module-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct module names. Overrides module-naming-
|
||||
# style.
|
||||
#module-rgx=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
# These decorators are taken in consideration only for invalid-name.
|
||||
property-classes=abc.abstractproperty
|
||||
|
||||
# Naming style matching correct variable names.
|
||||
variable-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct variable names. Overrides variable-
|
||||
# naming-style.
|
||||
#variable-rgx=
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=150
|
||||
|
||||
# Maximum number of lines in a module.
|
||||
max-module-lines=2000
|
||||
|
||||
# Allow the body of a class to be on the same line as the declaration if body
|
||||
# contains single statement.
|
||||
single-line-class-stmt=no
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# The type of string formatting that logging methods do. `old` means using %
|
||||
# formatting, `new` is for `{}` formatting.
|
||||
logging-format-style=old
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format.
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,
|
||||
XXX,
|
||||
TODO
|
||||
|
||||
# Regular expression of note tags to take in consideration.
|
||||
#notes-rgx=
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Comments are removed from the similarity computation
|
||||
ignore-comments=yes
|
||||
|
||||
# Docstrings are removed from the similarity computation
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Imports are removed from the similarity computation
|
||||
ignore-imports=no
|
||||
|
||||
# Signatures are removed from the similarity computation
|
||||
ignore-signatures=no
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Limits count of emitted suggestions for spelling mistakes.
|
||||
max-spelling-suggestions=4
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it work,
|
||||
# install the 'python-enchant' package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should be considered directives if they
|
||||
# appear and the beginning of a comment and should not be checked.
|
||||
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains the private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to the private dictionary (see the
|
||||
# --spelling-private-dict-file option) instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[STRING]
|
||||
|
||||
# This flag controls whether inconsistent-quotes generates a warning when the
|
||||
# character used as a quote delimiter is used inconsistently within a module.
|
||||
check-quote-consistency=no
|
||||
|
||||
# This flag controls whether the implicit-str-concat should generate a warning
|
||||
# on implicit string concatenation in sequences defined over several lines.
|
||||
check-str-concat-over-line-jumps=no
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=os.*
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# class is considered mixin if its name matches the mixin-class-rgx option.
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# Tells whether to warn about missing members when the owner of the attribute
|
||||
# is inferred to be None.
|
||||
ignore-none=yes
|
||||
|
||||
# This flag controls whether pylint should warn about no-member and similar
|
||||
# checks whenever an opaque object is returned when inferring. The inference
|
||||
# can return multiple potential results while evaluating a Python object, but
|
||||
# some branches might not be evaluated, which results in partial inference. In
|
||||
# that case, it might be useful to still emit no-member and other checks for
|
||||
# the rest of the inferred objects.
|
||||
ignore-on-opaque-inference=yes
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis). It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=
|
||||
|
||||
# Show a hint with possible names when a member name was not found. The aspect
|
||||
# of finding the hint is based on edit distance.
|
||||
missing-member-hint=yes
|
||||
|
||||
# The minimum edit distance a name should have in order to be considered a
|
||||
# similar match for a missing member name.
|
||||
missing-member-hint-distance=1
|
||||
|
||||
# The total number of similar names that should be taken in consideration when
|
||||
# showing a hint for a missing member.
|
||||
missing-member-max-choices=1
|
||||
|
||||
# Regex pattern to define which classes are considered mixins ignore-mixin-
|
||||
# members is set to 'yes'
|
||||
mixin-class-rgx=.*[Mm]ixin
|
||||
|
||||
# List of decorators that change the signature of a decorated function.
|
||||
signature-mutators=
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid defining new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# Tells whether unused global variables should be treated as a violation.
|
||||
allow-global-unused-variables=yes
|
||||
|
||||
# List of names allowed to shadow builtins
|
||||
allowed-redefined-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,
|
||||
_cb
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expected to
|
||||
# not be used).
|
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore.
|
||||
ignored-argument-names=_.*|^ignored_|^unused_
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# Warn about protected attribute access inside special methods
|
||||
check-protected-access-in-special-methods=no
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp,
|
||||
__post_init__
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# List of regular expressions of class ancestor names to ignore when counting
|
||||
# public methods (see R0903)
|
||||
exclude-too-few-public-methods=
|
||||
|
||||
# List of qualified class names to ignore when counting class parents (see
|
||||
# R0901)
|
||||
ignored-parents=
|
||||
|
||||
# Maximum number of arguments for function / method.
|
||||
max-args=8
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Maximum number of boolean expressions in an if statement (see R0916).
|
||||
max-bool-expr=5
|
||||
|
||||
# Maximum number of branch for function / method body.
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of locals for function / method body.
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of return / yield for function / method body.
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of statements in function / method body.
|
||||
max-statements=50
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# List of modules that can be imported at any level, not just the top level
|
||||
# one.
|
||||
allow-any-import-level=
|
||||
|
||||
# Allow wildcard imports from modules that define __all__.
|
||||
allow-wildcard-with-all=no
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma.
|
||||
deprecated-modules=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of external dependencies
|
||||
# to the given file (report RP0402 must not be disabled).
|
||||
ext-import-graph=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of all (i.e. internal and
|
||||
# external) dependencies to the given file (report RP0402 must not be
|
||||
# disabled).
|
||||
import-graph=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of internal dependencies
|
||||
# to the given file (report RP0402 must not be disabled).
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant
|
||||
|
||||
# Couples of modules and preferred modules, separated by a comma.
|
||||
preferred-modules=
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "BaseException, Exception".
|
||||
overgeneral-exceptions=BaseException,
|
||||
Exception
|
55
Dockerfile
55
Dockerfile
@ -1,28 +1,51 @@
|
||||
FROM python:alpine
|
||||
FROM ubuntu:20.04
|
||||
|
||||
ENV DEBIAN_FRONTEND="noninteractive"
|
||||
|
||||
LABEL maintainer="Dockerfile created by Zedifus <https://gitlab.com/zedifus>"
|
||||
|
||||
# Security Patch for CVE-2021-44228
|
||||
ENV LOG4J_FORMAT_MSG_NO_LOOKUPS=true
|
||||
|
||||
# Install Packages, Build Dependencies & Garbage Collect & Harden
|
||||
# (Alpine Edge repo is needed because jre16 is new)
|
||||
COPY requirements.txt /commander/requirements.txt
|
||||
RUN apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/community llvm11-libs openssl-dev rust cargo gcc musl-dev libffi-dev make openjdk8-jre-base openjdk11-jre-headless openjdk16-jre-headless mariadb-dev \
|
||||
&& pip3 install --no-cache-dir -r /commander/requirements.txt \
|
||||
&& apk del --no-cache gcc musl-dev libffi-dev make rust cargo openssl-dev llvm11-libs \
|
||||
&& rm -rf /sbin/apk \
|
||||
&& rm -rf /etc/apk \
|
||||
&& rm -rf /lib/apk \
|
||||
&& rm -rf /usr/share/apk \
|
||||
&& rm -rf /var/lib/apk
|
||||
# Create non-root user & required dirs
|
||||
RUN useradd -g root -M crafty \
|
||||
&& mkdir /commander \
|
||||
&& chown -R crafty:root /commander
|
||||
|
||||
# Copy Source & copy default config from image
|
||||
COPY ./ /commander
|
||||
# Install required system packages
|
||||
RUN apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
sudo \
|
||||
gcc \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
libmariadb-dev \
|
||||
default-jre \
|
||||
openjdk-8-jre-headless \
|
||||
openjdk-11-jre-headless \
|
||||
openjdk-16-jre-headless \
|
||||
openjdk-17-jre-headless \
|
||||
&& apt-get autoremove \
|
||||
&& apt-get clean
|
||||
|
||||
# Switch to service user for installing crafty deps
|
||||
USER crafty
|
||||
WORKDIR /commander
|
||||
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 -r requirements.txt \
|
||||
&& deactivate
|
||||
USER root
|
||||
|
||||
# Copy Source w/ perms & prepare default config from example
|
||||
COPY --chown=crafty:root ./ ./
|
||||
RUN mv ./app/config ./app/config_original \
|
||||
&& mv ./app/config_original/default.json.example ./app/config_original/default.json \
|
||||
&& chmod +x ./docker_launcher.sh
|
||||
&& mv ./app/config_original/default.json.example ./app/config_original/default.json \
|
||||
&& chmod +x ./docker_launcher.sh
|
||||
|
||||
# Expose Web Interface port & Server port range
|
||||
EXPOSE 8000
|
||||
|
67
README.md
67
README.md
@ -8,7 +8,7 @@ a web interface for the server administrators to interact with their servers. Cr
|
||||
is compatible with Docker, Linux, Windows 7, Windows 8 and Windows 10.
|
||||
|
||||
## Documentation
|
||||
Temporary documentation available on [GitLab](https://gitlab.com/crafty-controller/crafty-commander/wikis/home)
|
||||
Documentation available on [wiki.craftycontrol.com](https://craftycontrol.com)
|
||||
|
||||
## Meta
|
||||
Project Homepage - https://craftycontrol.com
|
||||
@ -17,15 +17,35 @@ Discord Server - https://discord.gg/9VJPhCE
|
||||
|
||||
Git Repository - https://gitlab.com/crafty-controller/crafty-web
|
||||
|
||||
## Basic Docker Usage
|
||||
<br>
|
||||
|
||||
**To get started with docker**, all you need to do is pull the image from this git repository's registry.
|
||||
This is done by using `docker-compose` or `docker run`(You don't need to clone the Repository and build, like in 3.x ).
|
||||
## Basic Docker Usage 🐳
|
||||
|
||||
If you have a config folder already from previous local installation or docker setup, the image should mount this volume, if none is present then it will populate its own config folder for you.
|
||||
With `Crafty Controller 4.0` we have focused on building our DevOps Principles, implementing build automation, and securing our containers, with the hopes of making our Container user's lives abit easier.
|
||||
|
||||
### Using the registry image:
|
||||
The provided image supports both `arm64` and `amd64` out the box, if you have issues though you can build it yourself.
|
||||
### - Two big changes you will notice is:
|
||||
- We now provide pre-built images for you guys.
|
||||
- Containers now run as non-root, using practices used by OpenShift & Kubernetes (root group perms).
|
||||
|
||||
|
||||
> __**⚠ 🔻WARNING: [WSL/WSL2 | WINDOWS 11 | DOCKER DESKTOP]🔻**__ <br>
|
||||
BE ADVISED! Upstream is currently broken for Minecraft running on **Docker under WSL/WSL2, Windows 11 / DOCKER DESKTOP!** <br>
|
||||
On '**Stop**' or '**Restart**' of the MC Server, there is a 90% chance the World's Chunks will be shredded irreparably! <br>
|
||||
Please only run Docker on Linux, If you are using Windows we have a portable installs found here: [Latest-Stable](https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build), [Latest-Development](https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build)
|
||||
|
||||
----
|
||||
|
||||
### - To get started with docker 🛫
|
||||
All you need to do is pull the image from this git repository's registry.
|
||||
This is done by using `'docker-compose'` or `'docker run'` (You don't need to clone the Repository and build, like in 3.x ).
|
||||
|
||||
If you have a config folder already from previous local installation or _docker setup_*, the image should mount this volume and fix the permission as required, if no config present then it will populate its own config folder for you. <br> <br>
|
||||
As the Dockerfile uses the permission structure of `crafty:root` **internally** there is no need to worry about matching the `UID` or `GID` on the host system :)
|
||||
|
||||
<br>
|
||||
|
||||
### - Using the registry image 🌎
|
||||
The provided image supports both `arm64` and `amd64` out the box, if you have issues though you can build it yourself with the `compose` file in `docker/`.
|
||||
|
||||
The image is located at: `registry.gitlab.com/crafty-controller/crafty-commander:latest`
|
||||
| Branch | Status |
|
||||
@ -50,13 +70,17 @@ or
|
||||
```bash
|
||||
$ echo <token> | docker login registry.gitlab.com -u <username> --password-stdin
|
||||
```
|
||||
or
|
||||
or
|
||||
```bash
|
||||
$ cat ~/my_password.txt | docker login registry.gitlab.com -u <username> --password-stdin
|
||||
```
|
||||
|
||||
Then use one of the following methods:
|
||||
#### docker-compose.yml
|
||||
### **docker-compose.yml:**
|
||||
```sh
|
||||
# Make your compose file
|
||||
$ vim docker-compose.yml
|
||||
```
|
||||
```yml
|
||||
version: '3'
|
||||
|
||||
@ -64,20 +88,27 @@ services:
|
||||
crafty:
|
||||
container_name: crafty_commander
|
||||
image: registry.gitlab.com/crafty-controller/crafty-commander:latest
|
||||
environment:
|
||||
- TZ=Etc/UTC
|
||||
ports:
|
||||
- "8000:8000" # HTTP
|
||||
- "8443:8443" # HTTPS
|
||||
- "8123:8123" # DYNMAP
|
||||
- "19132:19132/udp" # BEDROCK
|
||||
- "24000-25600:24000-25600" # MC SERV PORT RANGE
|
||||
- "25500-25600:25500-25600" # MC SERV PORT RANGE
|
||||
volumes:
|
||||
- ./docker/backups:/commander/backups
|
||||
- ./docker/logs:/commander/logs
|
||||
- ./docker/servers:/commander/servers
|
||||
- ./docker/config:/commander/app/config
|
||||
- ./docker/import:/commander/import
|
||||
```
|
||||
```sh
|
||||
$ docker-compose up -d && docker-compose logs -f
|
||||
```
|
||||
<br>
|
||||
|
||||
#### docker run
|
||||
### **docker run:**
|
||||
```sh
|
||||
$ docker run \
|
||||
--name crafty_commander \
|
||||
@ -85,19 +116,21 @@ $ docker run \
|
||||
-p 8443:8443 \
|
||||
-p 8123:8123 \
|
||||
-p 19132:19132/udp \
|
||||
-p 24000-25600:24000-25600 \
|
||||
-p 25500-25600:25500-25600 \
|
||||
-e TZ=Etc/UTC \
|
||||
-v "/$(pwd)/docker/backups:/commander/backups" \
|
||||
-v "/$(pwd)/docker/logs:/commander/logs" \
|
||||
-v "/$(pwd)/docker/servers:/commander/servers" \
|
||||
-v "/$(pwd)/docker/config:/commander/app/config" \
|
||||
-v "/$(pwd)/docker/import:/commander/import" \
|
||||
registry.gitlab.com/crafty-controller/crafty-commander:latest
|
||||
```
|
||||
|
||||
### Building from the cloned repository:
|
||||
### **Building from the cloned repository:**
|
||||
|
||||
If you are building from `docker-compose` you can find the compose file in `./docker/docker-compose.yml` just `cd` to the docker directory and `docker-compose up -d`
|
||||
|
||||
If you'd rather not use `docker-compose` you can use the following `docker run`in the directory where the *Dockerfile* is:
|
||||
If you'd rather not use `docker-compose` you can use the following `docker run` in the directory where the *Dockerfile* is:
|
||||
```sh
|
||||
# REMEMBER, Build your image first!
|
||||
$ docker build . -t crafty
|
||||
@ -108,11 +141,13 @@ $ docker run \
|
||||
-p 8443:8443 \
|
||||
-p 8123:8123 \
|
||||
-p 19132:19132/udp \
|
||||
-p 24000-25600:24000-25600 \
|
||||
-p 25500-25600:25500-25600 \
|
||||
-e TZ=Etc/UTC \
|
||||
-v "/$(pwd)/docker/backups:/commander/backups" \
|
||||
-v "/$(pwd)/docker/logs:/commander/logs" \
|
||||
-v "/$(pwd)/docker/servers:/commander/servers" \
|
||||
-v "/$(pwd)/docker/config:/commander/app/config" \
|
||||
-v "/$(pwd)/docker/import:/commander/import" \
|
||||
crafty
|
||||
```
|
||||
A fresh build will take several minutes depending on your system, but will be rapid there after.
|
||||
A fresh build will take several minutes depending on your system, but will be rapid thereafter.
|
||||
|
@ -1,23 +1,7 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
|
||||
from app.classes.models.users import ApiKeys
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -42,15 +26,15 @@ class Crafty_Perms_Controller:
|
||||
return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Server_Creation)
|
||||
|
||||
@staticmethod
|
||||
def can_add_user(user_id):
|
||||
#TODO: Complete if we need a User Addition limit
|
||||
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.User_Config)
|
||||
def can_add_user(): # Add back argument 'user_id' when you work on this
|
||||
#TODO: Complete if we need a User Addition limit
|
||||
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.User_Config)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def can_add_role(user_id):
|
||||
#TODO: Complete if we need a Role Addition limit
|
||||
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config)
|
||||
def can_add_role(): # Add back argument 'user_id' when you work on this
|
||||
#TODO: Complete if we need a Role Addition limit
|
||||
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
@ -70,3 +54,7 @@ class Crafty_Perms_Controller:
|
||||
@staticmethod
|
||||
def add_server_creation(user_id):
|
||||
return crafty_permissions.add_server_creation(user_id)
|
||||
|
||||
@staticmethod
|
||||
def get_api_key_permissions_list(key: ApiKeys):
|
||||
return crafty_permissions.get_api_key_permissions_list(key)
|
||||
|
@ -1,25 +1,8 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.management import management_helper
|
||||
from app.classes.models.servers import servers_helper
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Management_Controller:
|
||||
@ -31,10 +14,6 @@ class Management_Controller:
|
||||
def get_latest_hosts_stats():
|
||||
return management_helper.get_latest_hosts_stats()
|
||||
|
||||
@staticmethod
|
||||
def new_api_token():
|
||||
return management_helper.new_api_token()
|
||||
|
||||
#************************************************************************************************
|
||||
# Commands Methods
|
||||
#************************************************************************************************
|
||||
@ -44,19 +23,16 @@ class Management_Controller:
|
||||
|
||||
@staticmethod
|
||||
def send_command(user_id, server_id, remote_ip, command):
|
||||
|
||||
server_name = servers_helper.get_server_friendly_name(server_id)
|
||||
|
||||
# Example: Admin issued command start_server for server Survival
|
||||
management_helper.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name),
|
||||
server_id, remote_ip)
|
||||
|
||||
management_helper.add_to_audit_log(user_id, f"issued command {command} for server {server_name}", server_id, remote_ip)
|
||||
management_helper.add_command(server_id, user_id, remote_ip, command)
|
||||
|
||||
@staticmethod
|
||||
def mark_command_complete(command_id=None):
|
||||
return management_helper.mark_command_complete(command_id)
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Audit_Log Methods
|
||||
#************************************************************************************************
|
||||
@ -77,7 +53,16 @@ class Management_Controller:
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True):
|
||||
return management_helper.create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment, enabled)
|
||||
return management_helper.create_scheduled_task(
|
||||
server_id,
|
||||
action,
|
||||
interval,
|
||||
interval_type,
|
||||
start_time,
|
||||
command,
|
||||
comment,
|
||||
enabled
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete_scheduled_task(schedule_id):
|
||||
@ -91,6 +76,14 @@ class Management_Controller:
|
||||
def get_scheduled_task(schedule_id):
|
||||
return management_helper.get_scheduled_task(schedule_id)
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task_model(schedule_id):
|
||||
return management_helper.get_scheduled_task_model(schedule_id)
|
||||
|
||||
@staticmethod
|
||||
def get_child_schedules(sch_id):
|
||||
return management_helper.get_child_schedules(sch_id)
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_by_server(server_id):
|
||||
return management_helper.get_schedules_by_server(server_id)
|
||||
@ -111,5 +104,17 @@ class Management_Controller:
|
||||
return management_helper.get_backup_config(server_id)
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
return management_helper.set_backup_config(server_id, backup_path, max_backups, auto_enabled)
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None, compress: bool = False,):
|
||||
return management_helper.set_backup_config(server_id, backup_path, max_backups, excluded_dirs, compress)
|
||||
|
||||
@staticmethod
|
||||
def get_excluded_backup_dirs(server_id: int):
|
||||
return management_helper.get_excluded_backup_dirs(server_id)
|
||||
|
||||
@staticmethod
|
||||
def add_excluded_backup_dir(server_id: int, dir_to_add: str):
|
||||
management_helper.add_excluded_backup_dir(server_id, dir_to_add)
|
||||
|
||||
@staticmethod
|
||||
def del_excluded_backup_dir(server_id: int, dir_to_del: str):
|
||||
management_helper.del_excluded_backup_dir(server_id, dir_to_del)
|
||||
|
@ -1,25 +1,9 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.roles import roles_helper
|
||||
from app.classes.models.server_permissions import server_permissions
|
||||
from app.classes.models.users import users_helper
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -39,11 +23,12 @@ class Roles_Controller:
|
||||
|
||||
|
||||
@staticmethod
|
||||
def update_role(role_id, role_data={}, permissions_mask="00000000"):
|
||||
def update_role(role_id: str, role_data = None, permissions_mask: str = "00000000"):
|
||||
if role_data is None:
|
||||
role_data = {}
|
||||
base_data = Roles_Controller.get_role_with_servers(role_id)
|
||||
up_data = {}
|
||||
added_servers = set()
|
||||
edited_servers = set()
|
||||
removed_servers = set()
|
||||
for key in role_data:
|
||||
if key == "role_id":
|
||||
@ -54,7 +39,7 @@ class Roles_Controller:
|
||||
elif base_data[key] != role_data[key]:
|
||||
up_data[key] = role_data[key]
|
||||
up_data['last_update'] = helper.get_time_as_string()
|
||||
logger.debug("role: {} +server:{} -server{}".format(role_data, added_servers, removed_servers))
|
||||
logger.debug(f"role: {role_data} +server:{added_servers} -server{removed_servers}")
|
||||
for server in added_servers:
|
||||
server_permissions.get_or_create(role_id, server, permissions_mask)
|
||||
for server in base_data['servers']:
|
||||
@ -94,4 +79,4 @@ class Roles_Controller:
|
||||
return role
|
||||
else:
|
||||
#logger.debug("role: ({}) {}".format(role_id, {}))
|
||||
return {}
|
||||
return {}
|
||||
|
@ -1,32 +1,19 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.main_models import db_helper
|
||||
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
|
||||
from app.classes.models.users import users_helper
|
||||
from app.classes.models.users import users_helper, ApiKeys
|
||||
from app.classes.models.roles import roles_helper
|
||||
from app.classes.models.servers import servers_helper
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
from app.classes.shared.main_models import db_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Server_Perms_Controller:
|
||||
|
||||
@staticmethod
|
||||
def get_server_user_list(server_id):
|
||||
return server_permissions.get_server_user_list(server_id)
|
||||
|
||||
@staticmethod
|
||||
def list_defined_permissions():
|
||||
permissions_list = server_permissions.get_permissions_list()
|
||||
@ -42,15 +29,23 @@ class Server_Perms_Controller:
|
||||
permissions_list = server_permissions.get_role_permissions_list(role_id)
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def get_server_permissions_foruser(user_id, server_id):
|
||||
permissions_list = server_permissions.get_user_permissions_list(user_id, server_id)
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def add_role_server(server_id, role_id, rs_permissions="00000000"):
|
||||
return server_permissions.add_role_server(server_id, role_id, rs_permissions)
|
||||
|
||||
@staticmethod
|
||||
def get_server_roles(server_id):
|
||||
return server_permissions.get_server_roles(server_id)
|
||||
|
||||
@staticmethod
|
||||
def backup_role_swap(old_server_id, new_server_id):
|
||||
role_list = server_permissions.get_server_roles(old_server_id)
|
||||
for role in role_list:
|
||||
server_permissions.add_role_server(
|
||||
new_server_id, role.role_id,
|
||||
server_permissions.get_permissions_mask(int(role.role_id), int(old_server_id)))
|
||||
#server_permissions.add_role_server(new_server_id, role.role_id, '00001000')
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Permissions Methods
|
||||
#************************************************************************************************
|
||||
@ -67,8 +62,17 @@ class Server_Perms_Controller:
|
||||
return server_permissions.get_role_permissions_list(role_id)
|
||||
|
||||
@staticmethod
|
||||
def get_user_permissions_list(user_id, server_id):
|
||||
return server_permissions.get_user_permissions_list(user_id, server_id)
|
||||
def get_user_id_permissions_list(user_id: str, server_id: str):
|
||||
return server_permissions.get_user_id_permissions_list(user_id, server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_api_key_id_permissions_list(key_id: str, server_id: str):
|
||||
key = users_helper.get_user_api_key(key_id)
|
||||
return server_permissions.get_api_key_permissions_list(key, server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
|
||||
return server_permissions.get_api_key_permissions_list(key, server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats_from_roles(user_id):
|
||||
|
@ -1,29 +1,13 @@
|
||||
from app.classes.controllers.roles_controller import Roles_Controller
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import json
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.main_models import db_helper
|
||||
from app.classes.controllers.roles_controller import Roles_Controller
|
||||
from app.classes.models.servers import servers_helper
|
||||
from app.classes.models.roles import roles_helper
|
||||
from app.classes.models.users import users_helper
|
||||
from app.classes.models.users import users_helper, ApiKeys
|
||||
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.main_models import db_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -33,8 +17,48 @@ class Servers_Controller:
|
||||
# Generic Servers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
return servers_helper.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
|
||||
def create_server(
|
||||
name: str,
|
||||
server_uuid: str,
|
||||
server_dir: str,
|
||||
backup_path: str,
|
||||
server_command: str,
|
||||
server_file: str,
|
||||
server_log_file: str,
|
||||
server_stop: str,
|
||||
server_type: str,
|
||||
server_port=25565):
|
||||
return servers_helper.create_server(
|
||||
name,
|
||||
server_uuid,
|
||||
server_dir,
|
||||
backup_path,
|
||||
server_command,
|
||||
server_file,
|
||||
server_log_file,
|
||||
server_stop,
|
||||
server_type,
|
||||
server_port)
|
||||
|
||||
@staticmethod
|
||||
def get_server_obj(server_id):
|
||||
return servers_helper.get_server_obj(server_id)
|
||||
|
||||
@staticmethod
|
||||
def update_server(server_obj):
|
||||
return servers_helper.update_server(server_obj)
|
||||
|
||||
@staticmethod
|
||||
def set_download(server_id):
|
||||
return servers_helper.set_download(server_id)
|
||||
|
||||
@staticmethod
|
||||
def finish_download(server_id):
|
||||
return servers_helper.finish_download(server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_download_status(server_id):
|
||||
return servers_helper.get_download_status(server_id)
|
||||
|
||||
@staticmethod
|
||||
def remove_server(server_id):
|
||||
@ -73,6 +97,22 @@ class Servers_Controller:
|
||||
def get_all_servers_stats():
|
||||
return servers_helper.get_all_servers_stats()
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats_api_key(api_key: ApiKeys):
|
||||
server_data = []
|
||||
authorized_servers = Servers_Controller.get_authorized_servers(api_key.user.user_id)
|
||||
|
||||
for s in authorized_servers:
|
||||
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
|
||||
key_permissions = server_permissions.get_api_key_permissions_list(api_key, s.get('server_id'))
|
||||
if Enum_Permissions_Server.Commands in key_permissions:
|
||||
user_command_permission = True
|
||||
else:
|
||||
user_command_permission = False
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0],
|
||||
"user_command_permission": user_command_permission})
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats(user_id):
|
||||
server_data = []
|
||||
@ -80,12 +120,18 @@ class Servers_Controller:
|
||||
|
||||
for s in authorized_servers:
|
||||
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
|
||||
user_permissions = server_permissions.get_user_permissions_list(user_id, s.get('server_id'))
|
||||
# TODO
|
||||
user_permissions = server_permissions.get_user_id_permissions_list(user_id, s.get('server_id'))
|
||||
if Enum_Permissions_Server.Commands in user_permissions:
|
||||
user_command_permission = True
|
||||
else:
|
||||
user_command_permission = False
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":user_command_permission})
|
||||
server_data.append({
|
||||
'server_data': s,
|
||||
'stats': db_helper.return_rows(latest)[0],
|
||||
'user_command_permission': user_command_permission
|
||||
})
|
||||
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
@ -104,17 +150,28 @@ class Servers_Controller:
|
||||
return servers_helper.server_id_exists(server_id)
|
||||
|
||||
@staticmethod
|
||||
def server_id_authorized(serverId, user_id):
|
||||
authorized = 0
|
||||
def get_server_type_by_id(server_id):
|
||||
return servers_helper.get_server_type_by_id(server_id)
|
||||
|
||||
@staticmethod
|
||||
def server_id_authorized(server_id_a, user_id):
|
||||
user_roles = users_helper.user_role_query(user_id)
|
||||
for role in user_roles:
|
||||
authorized = server_permissions.get_role_servers_from_role_id(role.role_id)
|
||||
for server_id_b in server_permissions.get_role_servers_from_role_id(role.role_id):
|
||||
if str(server_id_a) == str(server_id_b.server_id):
|
||||
return True
|
||||
return False
|
||||
|
||||
#authorized = db_helper.return_rows(authorized)
|
||||
@staticmethod
|
||||
def is_crashed(server_id):
|
||||
return servers_helper.is_crashed(server_id)
|
||||
|
||||
if authorized.count() == 0:
|
||||
return False
|
||||
return True
|
||||
@staticmethod
|
||||
def server_id_authorized_api_key(server_id: str, api_key: ApiKeys) -> bool:
|
||||
# TODO
|
||||
return Servers_Controller.server_id_authorized(server_id, api_key.user.user_id)
|
||||
# There is no view server permission
|
||||
# permission_helper.both_have_perm(api_key)
|
||||
|
||||
@staticmethod
|
||||
def set_update(server_id, value):
|
||||
@ -136,6 +193,10 @@ class Servers_Controller:
|
||||
def get_waiting_start(server_id):
|
||||
return servers_helper.get_waiting_start(server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_update_status(server_id):
|
||||
return servers_helper.get_update_status(server_id)
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Helpers Methods
|
||||
#************************************************************************************************
|
||||
@ -146,7 +207,7 @@ class Servers_Controller:
|
||||
path = os.path.join(server_path, 'banned-players.json')
|
||||
|
||||
try:
|
||||
with open(path) as file:
|
||||
with open(helper.get_os_understandable_path(path), encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
file.close()
|
||||
except Exception as ex:
|
||||
@ -170,7 +231,6 @@ class Servers_Controller:
|
||||
))
|
||||
for log_file in log_files:
|
||||
log_file_path = os.path.join(logs_path, log_file)
|
||||
if self.check_file_exists(log_file_path) and \
|
||||
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
|
||||
if helper.check_file_exists(log_file_path) and \
|
||||
helper.is_file_older_than_x_days(log_file_path, logs_delete_after):
|
||||
os.remove(log_file_path)
|
||||
|
||||
|
@ -1,25 +1,15 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
from typing import Optional
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.users import Users, users_helper
|
||||
from app.classes.models.users import users_helper
|
||||
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
|
||||
from app.classes.models.management import management_helper
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.authentication import authentication
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Users_Controller:
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Users Methods
|
||||
#************************************************************************************************
|
||||
@ -31,10 +21,6 @@ class Users_Controller:
|
||||
def get_id_by_name(username):
|
||||
return users_helper.get_user_id_by_name(username)
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_api_token(token: str):
|
||||
return users_helper.get_user_by_api_token(token)
|
||||
|
||||
@staticmethod
|
||||
def get_user_lang_by_id(user_id):
|
||||
return users_helper.get_user_lang_by_id(user_id)
|
||||
@ -43,26 +29,38 @@ class Users_Controller:
|
||||
def get_user_by_id(user_id):
|
||||
return users_helper.get_user(user_id)
|
||||
|
||||
@staticmethod
|
||||
def update_server_order(user_id, user_server_order):
|
||||
users_helper.update_server_order(user_id, user_server_order)
|
||||
|
||||
@staticmethod
|
||||
def get_server_order(user_id):
|
||||
return users_helper.get_server_order(user_id)
|
||||
|
||||
@staticmethod
|
||||
def user_query(user_id):
|
||||
return users_helper.user_query(user_id)
|
||||
|
||||
@staticmethod
|
||||
def update_user(user_id, user_data={}, user_crafty_data={}):
|
||||
def set_support_path(user_id, support_path):
|
||||
users_helper.set_support_path(user_id, support_path)
|
||||
|
||||
@staticmethod
|
||||
def update_user(user_id: str, user_data=None, user_crafty_data=None):
|
||||
if user_crafty_data is None:
|
||||
user_crafty_data = {}
|
||||
if user_data is None:
|
||||
user_data = {}
|
||||
base_data = users_helper.get_user(user_id)
|
||||
up_data = {}
|
||||
added_roles = set()
|
||||
removed_roles = set()
|
||||
removed_servers = set()
|
||||
for key in user_data:
|
||||
if key == "user_id":
|
||||
continue
|
||||
elif key == "roles":
|
||||
added_roles = user_data['roles'].difference(base_data['roles'])
|
||||
removed_roles = base_data['roles'].difference(user_data['roles'])
|
||||
elif key == "regen_api":
|
||||
if user_data['regen_api']:
|
||||
up_data['api_token'] = management_helper.new_api_token()
|
||||
elif key == "password":
|
||||
if user_data['password'] is not None and user_data['password'] != "":
|
||||
up_data['password'] = helper.encode_pass(user_data['password'])
|
||||
@ -70,16 +68,15 @@ class Users_Controller:
|
||||
up_data[key] = user_data[key]
|
||||
up_data['last_update'] = helper.get_time_as_string()
|
||||
up_data['lang'] = user_data['lang']
|
||||
logger.debug("user: {} +role:{} -role:{}".format(user_data, added_roles, removed_roles))
|
||||
logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}")
|
||||
for role in added_roles:
|
||||
users_helper.get_or_create(user_id=user_id, role_id=role)
|
||||
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
|
||||
permissions_mask = user_crafty_data.get('permissions_mask', '000')
|
||||
|
||||
if 'server_quantity' in user_crafty_data:
|
||||
limit_server_creation = user_crafty_data['server_quantity'][
|
||||
Enum_Permissions_Crafty.Server_Creation.name]
|
||||
|
||||
for key in user_crafty_data:
|
||||
if key == "permissions_mask":
|
||||
permissions_mask = user_crafty_data['permissions_mask']
|
||||
if key == "server_quantity":
|
||||
limit_server_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Server_Creation.name]
|
||||
limit_user_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.User_Config.name]
|
||||
limit_role_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Roles_Config.name]
|
||||
else:
|
||||
@ -87,15 +84,20 @@ class Users_Controller:
|
||||
limit_user_creation = 0
|
||||
limit_role_creation = 0
|
||||
|
||||
crafty_permissions.add_or_update_user(user_id, permissions_mask, limit_server_creation, limit_user_creation, limit_role_creation)
|
||||
crafty_permissions.add_or_update_user(
|
||||
user_id,
|
||||
permissions_mask,
|
||||
limit_server_creation,
|
||||
limit_user_creation,
|
||||
limit_role_creation)
|
||||
|
||||
users_helper.delete_user_roles(user_id, removed_roles)
|
||||
users_helper.delete_user_roles(user_id, removed_roles)
|
||||
|
||||
users_helper.update_user(user_id, up_data)
|
||||
|
||||
@staticmethod
|
||||
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
|
||||
return users_helper.add_user(username, password=password, api_token=api_token, enabled=enabled, superuser=superuser)
|
||||
def add_user(username, password=None, email="default@example.com", enabled: bool = True, superuser: bool = False):
|
||||
return users_helper.add_user(username, password=password, email=email, enabled=enabled, superuser=superuser)
|
||||
|
||||
@staticmethod
|
||||
def remove_user(user_id):
|
||||
@ -105,10 +107,20 @@ class Users_Controller:
|
||||
def user_id_exists(user_id):
|
||||
return users_helper.user_id_exists(user_id)
|
||||
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_user_id_by_api_token(token: str) -> str:
|
||||
token_data = authentication.check_no_iat(token)
|
||||
return token_data['user_id']
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_api_token(token: str):
|
||||
_, user = authentication.check(token)
|
||||
return user
|
||||
|
||||
# ************************************************************************************************
|
||||
# User Roles Methods
|
||||
#************************************************************************************************
|
||||
|
||||
# ************************************************************************************************
|
||||
|
||||
@staticmethod
|
||||
def get_user_roles_id(user_id):
|
||||
return users_helper.get_user_roles_id(user_id)
|
||||
@ -124,7 +136,33 @@ class Users_Controller:
|
||||
@staticmethod
|
||||
def add_user_roles(user):
|
||||
return users_helper.add_user_roles(user)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def user_role_query(user_id):
|
||||
return users_helper.user_role_query(user_id)
|
||||
|
||||
# ************************************************************************************************
|
||||
# Api Keys Methods
|
||||
# ************************************************************************************************
|
||||
|
||||
@staticmethod
|
||||
def get_user_api_keys(user_id: str):
|
||||
return users_helper.get_user_api_keys(user_id)
|
||||
|
||||
@staticmethod
|
||||
def get_user_api_key(key_id: str):
|
||||
return users_helper.get_user_api_key(key_id)
|
||||
|
||||
@staticmethod
|
||||
def add_user_api_key(name: str, user_id: str, superuser: bool = False,
|
||||
server_permissions_mask: Optional[str] = None,
|
||||
crafty_permissions_mask: Optional[str] = None):
|
||||
return users_helper.add_user_api_key(name, user_id, superuser, server_permissions_mask, crafty_permissions_mask)
|
||||
|
||||
@staticmethod
|
||||
def delete_user_api_keys(user_id: str):
|
||||
return users_helper.delete_user_api_keys(user_id)
|
||||
|
||||
@staticmethod
|
||||
def delete_user_api_key(key_id: str):
|
||||
return users_helper.delete_user_api_key(key_id)
|
||||
|
107
app/classes/minecraft/bedrock_ping.py
Normal file
107
app/classes/minecraft/bedrock_ping.py
Normal file
@ -0,0 +1,107 @@
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
import psutil
|
||||
|
||||
class BedrockPing:
|
||||
magic = b'\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x12\x34\x56\x78'
|
||||
fields = { # (len, signed)
|
||||
"byte": (1, False),
|
||||
"long": (8, True),
|
||||
"ulong": (8, False),
|
||||
"magic": (16, False),
|
||||
"short": (2, True),
|
||||
"ushort": (2, False), #unsigned short
|
||||
"string": (2, False), #strlen is ushort
|
||||
"bool": (1, False),
|
||||
"address": (7, False),
|
||||
"uint24le": (3, False)
|
||||
}
|
||||
byte_order = 'big'
|
||||
|
||||
def __init__(self, bedrock_addr, bedrock_port, client_guid=0, timeout=5):
|
||||
self.addr = bedrock_addr
|
||||
self.port = bedrock_port
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock.settimeout(timeout)
|
||||
self.proc = psutil.Process(os.getpid())
|
||||
self.guid = client_guid
|
||||
self.guid_bytes = self.guid.to_bytes(8, BedrockPing.byte_order)
|
||||
|
||||
@staticmethod
|
||||
def __byter(in_val, to_type):
|
||||
f = BedrockPing.fields[to_type]
|
||||
return in_val.to_bytes(f[0], BedrockPing.byte_order, signed=f[1])
|
||||
|
||||
@staticmethod
|
||||
def __slice(in_bytes, pattern):
|
||||
ret = []
|
||||
bi = 0 # bytes index
|
||||
pi = 0 # pattern index
|
||||
while bi < len(in_bytes):
|
||||
try:
|
||||
f = BedrockPing.fields[pattern[pi]]
|
||||
except IndexError as index_error:
|
||||
raise IndexError("Ran out of pattern with additional bytes remaining") from index_error
|
||||
if pattern[pi] == "string":
|
||||
shl = f[0] # string header length
|
||||
sl = int.from_bytes(in_bytes[bi:bi+shl], BedrockPing.byte_order, signed=f[1]) # string length
|
||||
l = shl+sl
|
||||
ret.append(in_bytes[bi+shl:bi+shl+sl].decode('ascii'))
|
||||
elif pattern[pi] == "magic":
|
||||
l = f[0] # length of field
|
||||
ret.append(in_bytes[bi:bi+l])
|
||||
else:
|
||||
l = f[0] # length of field
|
||||
ret.append(int.from_bytes(in_bytes[bi:bi+l], BedrockPing.byte_order, signed=f[1]))
|
||||
bi+=l
|
||||
pi+=1
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def __get_time():
|
||||
#return time.time_ns() // 1000000
|
||||
return time.perf_counter_ns() // 1000000
|
||||
|
||||
def __sendping(self):
|
||||
pack_id = BedrockPing.__byter(0x01, 'byte')
|
||||
now = BedrockPing.__byter(BedrockPing.__get_time(), 'ulong')
|
||||
guid = self.guid_bytes
|
||||
d2s = pack_id+now+BedrockPing.magic+guid
|
||||
#print("S:", d2s)
|
||||
self.sock.sendto(d2s, (self.addr, self.port))
|
||||
|
||||
def __recvpong(self):
|
||||
data = self.sock.recv(4096)
|
||||
if data[0] == 0x1c:
|
||||
ret = {}
|
||||
sliced = BedrockPing.__slice(data,["byte","ulong","ulong","magic","string"])
|
||||
if sliced[3] != BedrockPing.magic:
|
||||
raise ValueError(f"Incorrect magic received ({sliced[3]})")
|
||||
ret["server_guid"] = sliced[2]
|
||||
ret["server_string_raw"] = sliced[4]
|
||||
server_info = sliced[4].split(';')
|
||||
ret["server_edition"] = server_info[0]
|
||||
ret["server_motd"] = (server_info[1], server_info[7])
|
||||
ret["server_protocol_version"] = server_info[2]
|
||||
ret["server_version_name"] = server_info[3]
|
||||
ret["server_player_count"] = server_info[4]
|
||||
ret["server_player_max"] = server_info[5]
|
||||
ret["server_uuid"] = server_info[6]
|
||||
ret["server_game_mode"] = server_info[8]
|
||||
ret["server_game_mode_num"] = server_info[9]
|
||||
ret["server_port_ipv4"] = server_info[10]
|
||||
ret["server_port_ipv6"] = server_info[11]
|
||||
return ret
|
||||
else:
|
||||
raise ValueError(f"Incorrect packet type ({data[0]} detected")
|
||||
|
||||
def ping(self, retries=3):
|
||||
rtr = retries
|
||||
while rtr > 0:
|
||||
try:
|
||||
self.__sendping()
|
||||
return self.__recvpong()
|
||||
except ValueError as e:
|
||||
print(f"E: {e}, checking next packet. Retries remaining: {rtr}/{retries}")
|
||||
rtr -= 1
|
@ -1,16 +1,18 @@
|
||||
from app.classes.shared.helpers import Helpers
|
||||
import struct
|
||||
import socket
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging.config
|
||||
import uuid
|
||||
import random
|
||||
|
||||
from app.classes.minecraft.bedrock_ping import BedrockPing
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self, data):
|
||||
self.description = data.get('description')
|
||||
@ -41,7 +43,7 @@ class Server:
|
||||
if "obfuscated" in e.keys():
|
||||
lines.append(get_code_format("obfuscated"))
|
||||
if "color" in e.keys():
|
||||
lines.append(get_code_format(e['color']))
|
||||
lines.append(get_code_format(e['color']))
|
||||
#Then append the text
|
||||
if "text" in e.keys():
|
||||
if e['text'] == '\n':
|
||||
@ -57,7 +59,11 @@ class Server:
|
||||
self.description = self.description['text']
|
||||
|
||||
self.icon = base64.b64decode(data.get('favicon', '')[22:])
|
||||
self.players = Players(data['players']).report()
|
||||
try:
|
||||
self.players = Players(data['players']).report()
|
||||
except KeyError:
|
||||
logger.error("Error geting player information key error")
|
||||
self.players = []
|
||||
self.version = data['version']['name']
|
||||
self.protocol = data['version']['protocol']
|
||||
|
||||
@ -101,13 +107,13 @@ def get_code_format(format_name):
|
||||
if format_name in data.keys():
|
||||
return data.get(format_name)
|
||||
else:
|
||||
logger.error("Format MOTD Error: format name {} does not exist".format(format_name))
|
||||
console.error("Format MOTD Error: format name {} does not exist".format(format_name))
|
||||
logger.error(f"Format MOTD Error: format name {format_name} does not exist")
|
||||
console.error(f"Format MOTD Error: format name {format_name} does not exist")
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Config File Error: Unable to read {} due to {}".format(format_file, e))
|
||||
console.critical("Config File Error: Unable to read {} due to {}".format(format_file, e))
|
||||
logger.critical(f"Config File Error: Unable to read {format_file} due to {e}")
|
||||
console.critical(f"Config File Error: Unable to read {format_file} due to {e}")
|
||||
|
||||
return ""
|
||||
|
||||
@ -126,15 +132,14 @@ def ping(ip, port):
|
||||
j += 1
|
||||
if j > 5:
|
||||
raise ValueError('var_int too big')
|
||||
if not (k & 0x80):
|
||||
if not k & 0x80:
|
||||
return i
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.connect((ip, port))
|
||||
|
||||
except socket.error as err:
|
||||
pass
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
try:
|
||||
@ -163,7 +168,25 @@ def ping(ip, port):
|
||||
return False
|
||||
|
||||
data += chunk
|
||||
logger.debug("Server reports this data on ping: {}".format(data))
|
||||
return Server(json.loads(data))
|
||||
logger.debug(f"Server reports this data on ping: {data}")
|
||||
try:
|
||||
return Server(json.loads(data))
|
||||
except KeyError:
|
||||
return {}
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
# For the rest of requests see wiki.vg/Protocol
|
||||
def ping_bedrock(ip, port):
|
||||
rd = random.Random()
|
||||
try:
|
||||
#pylint: disable=consider-using-f-string
|
||||
rd.seed(''.join(re.findall('..', '%012x' % uuid.getnode())))
|
||||
client_guid = uuid.UUID(int=rd.getrandbits(32)).int
|
||||
except:
|
||||
client_guid = 0
|
||||
try:
|
||||
brp = BedrockPing(ip, port, client_guid)
|
||||
return brp.ping()
|
||||
except:
|
||||
logger.debug("Unable to get RakNet stats")
|
||||
|
@ -1,66 +1,66 @@
|
||||
import pprint
|
||||
import os
|
||||
|
||||
class ServerProps:
|
||||
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.props = self._parse()
|
||||
|
||||
def _parse(self):
|
||||
"""Loads and parses the file speified in self.filepath"""
|
||||
with open(self.filepath) as fp:
|
||||
line = fp.readline()
|
||||
d = {}
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
while line:
|
||||
if '#' != line[0]:
|
||||
s = line
|
||||
s1 = s[:s.find('=')]
|
||||
if '\n' in s:
|
||||
s2 = s[s.find('=')+1:s.find('\n')]
|
||||
else:
|
||||
s2 = s[s.find('=')+1:]
|
||||
d[s1] = s2
|
||||
else:
|
||||
with open(".header", "a+") as h:
|
||||
h.write(line)
|
||||
line = fp.readline()
|
||||
return d
|
||||
|
||||
def print(self):
|
||||
"""Prints the properties dictionary (using pprint)"""
|
||||
pprint.pprint(self.props)
|
||||
|
||||
def get(self):
|
||||
"""Returns the properties dictionary"""
|
||||
return self.props
|
||||
|
||||
def update(self, key, val):
|
||||
"""Updates property in the properties dictionary [ update("pvp", "true") ] and returns boolean condition"""
|
||||
if key in self.props.keys():
|
||||
self.props[key] = val
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def save(self):
|
||||
"""Writes to the new file"""
|
||||
with open(self.filepath, "a+") as f:
|
||||
f.truncate(0)
|
||||
with open(".header") as header:
|
||||
line = header.readline()
|
||||
while line:
|
||||
f.write(line)
|
||||
line = header.readline()
|
||||
header.close()
|
||||
for key, value in self.props.items():
|
||||
f.write(key + "=" + value + "\n")
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
|
||||
@staticmethod
|
||||
def cleanup():
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
import pprint
|
||||
import os
|
||||
|
||||
class ServerProps:
|
||||
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.props = self._parse()
|
||||
|
||||
def _parse(self):
|
||||
"""Loads and parses the file specified in self.filepath"""
|
||||
with open(self.filepath, encoding='utf-8') as fp:
|
||||
line = fp.readline()
|
||||
d = {}
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
while line:
|
||||
if '#' != line[0]:
|
||||
s = line
|
||||
s1 = s[:s.find('=')]
|
||||
if '\n' in s:
|
||||
s2 = s[s.find('=')+1:s.find('\n')]
|
||||
else:
|
||||
s2 = s[s.find('=')+1:]
|
||||
d[s1] = s2
|
||||
else:
|
||||
with open(".header", "a+", encoding='utf-8') as h:
|
||||
h.write(line)
|
||||
line = fp.readline()
|
||||
return d
|
||||
|
||||
def print(self):
|
||||
"""Prints the properties dictionary (using pprint)"""
|
||||
pprint.pprint(self.props)
|
||||
|
||||
def get(self):
|
||||
"""Returns the properties dictionary"""
|
||||
return self.props
|
||||
|
||||
def update(self, key, val):
|
||||
"""Updates property in the properties dictionary [ update("pvp", "true") ] and returns boolean condition"""
|
||||
if key in self.props.keys():
|
||||
self.props[key] = val
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def save(self):
|
||||
"""Writes to the new file"""
|
||||
with open(self.filepath, "a+", encoding='utf-8') as f:
|
||||
f.truncate(0)
|
||||
with open(".header", encoding='utf-8') as header:
|
||||
line = header.readline()
|
||||
while line:
|
||||
f.write(line)
|
||||
line = header.readline()
|
||||
header.close()
|
||||
for key, value in self.props.items():
|
||||
f.write(key + "=" + value + "\n")
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
|
||||
@staticmethod
|
||||
def cleanup():
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
|
@ -1,5 +1,3 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
@ -7,10 +5,9 @@ import shutil
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from app.classes.controllers.servers_controller import Servers_Controller
|
||||
from app.classes.models.server_permissions import server_permissions
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -18,11 +15,8 @@ logger = logging.getLogger(__name__)
|
||||
try:
|
||||
import requests
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
except ModuleNotFoundError as err:
|
||||
helper.auto_installer_fix(err)
|
||||
|
||||
class ServerJars:
|
||||
|
||||
@ -30,7 +24,7 @@ class ServerJars:
|
||||
self.base_url = "https://serverjars.com"
|
||||
|
||||
def _get_api_result(self, call_url: str):
|
||||
full_url = "{base}{call_url}".format(base=self.base_url, call_url=call_url)
|
||||
full_url = f"{self.base_url}{call_url}"
|
||||
|
||||
try:
|
||||
r = requests.get(full_url, timeout=2)
|
||||
@ -38,20 +32,20 @@ class ServerJars:
|
||||
if r.status_code not in [200, 201]:
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error("Unable to connect to serverjar.com api due to error: {}".format(e))
|
||||
logger.error(f"Unable to connect to serverjar.com api due to error: {e}")
|
||||
return {}
|
||||
|
||||
try:
|
||||
api_data = json.loads(r.content)
|
||||
except Exception as e:
|
||||
logger.error("Unable to parse serverjar.com api result due to error: {}".format(e))
|
||||
logger.error(f"Unable to parse serverjar.com api result due to error: {e}")
|
||||
return {}
|
||||
|
||||
api_result = api_data.get('status')
|
||||
api_response = api_data.get('response', {})
|
||||
|
||||
if api_result != "success":
|
||||
logger.error("Api returned a failed status: {}".format(api_result))
|
||||
logger.error(f"Api returned a failed status: {api_result}")
|
||||
return {}
|
||||
|
||||
return api_response
|
||||
@ -61,11 +55,11 @@ class ServerJars:
|
||||
cache_file = helper.serverjar_cache
|
||||
cache = {}
|
||||
try:
|
||||
with open(cache_file, "r") as f:
|
||||
with open(cache_file, "r", encoding='utf-8') as f:
|
||||
cache = json.load(f)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to read serverjars.com cache file: {}".format(e))
|
||||
logger.error(f"Unable to read serverjars.com cache file: {e}")
|
||||
|
||||
return cache
|
||||
|
||||
@ -99,7 +93,7 @@ class ServerJars:
|
||||
def _check_api_alive(self):
|
||||
logger.info("Checking serverjars.com API status")
|
||||
|
||||
check_url = "{base}/api/fetchTypes".format(base=self.base_url)
|
||||
check_url = f"{self.base_url}/api/fetchTypes"
|
||||
try:
|
||||
r = requests.get(check_url, timeout=2)
|
||||
|
||||
@ -107,7 +101,7 @@ class ServerJars:
|
||||
logger.info("Serverjars.com API is alive")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Unable to connect to serverjar.com api due to error: {}".format(e))
|
||||
logger.error(f"Unable to connect to serverjar.com api due to error: {e}")
|
||||
return {}
|
||||
|
||||
logger.error("unable to contact serverjars.com api")
|
||||
@ -153,15 +147,15 @@ class ServerJars:
|
||||
|
||||
# save our cache
|
||||
try:
|
||||
with open(cache_file, "w") as f:
|
||||
with open(cache_file, "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(data, indent=4))
|
||||
logger.info("Cache file refreshed")
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to update serverjars.com cache file: {}".format(e))
|
||||
logger.error(f"Unable to update serverjars.com cache file: {e}")
|
||||
|
||||
def _get_jar_details(self, jar_type='servers'):
|
||||
url = '/api/fetchAll/{type}'.format(type=jar_type)
|
||||
url = f'/api/fetchAll/{jar_type}'
|
||||
response = self._get_api_result(url)
|
||||
temp = []
|
||||
for v in response:
|
||||
@ -174,26 +168,53 @@ class ServerJars:
|
||||
response = self._get_api_result(url)
|
||||
return response
|
||||
|
||||
def download_jar(self, server, version, path, name):
|
||||
update_thread = threading.Thread(target=self.a_download_jar, daemon=True, name="exe_download", args=(server, version, path, name))
|
||||
def download_jar(self, server, version, path, server_id):
|
||||
update_thread = threading.Thread(target=self.a_download_jar, daemon=True, args=(server, version, path, server_id))
|
||||
update_thread.start()
|
||||
|
||||
def a_download_jar(self, server, version, path, name):
|
||||
fetch_url = "{base}/api/fetchJar/{server}/{version}".format(base=self.base_url, server=server, version=version)
|
||||
def a_download_jar(self, server, version, path, server_id):
|
||||
#delaying download for server register to finish
|
||||
time.sleep(3)
|
||||
fetch_url = f"{self.base_url}/api/fetchJar/{server}/{version}"
|
||||
server_users = server_permissions.get_server_user_list(server_id)
|
||||
|
||||
|
||||
#We need to make sure the server is registered before we submit a db update for it's stats.
|
||||
while True:
|
||||
try:
|
||||
Servers_Controller.set_download(server_id)
|
||||
for user in server_users:
|
||||
websocket_helper.broadcast_user(user, 'send_start_reload', {
|
||||
})
|
||||
|
||||
break
|
||||
except:
|
||||
logger.debug("server not registered yet. Delaying download.")
|
||||
|
||||
# open a file stream
|
||||
with requests.get(fetch_url, timeout=2, stream=True) as r:
|
||||
try:
|
||||
with open(path, 'wb') as output:
|
||||
shutil.copyfileobj(r.raw, output)
|
||||
Servers_Controller.finish_download(server_id)
|
||||
|
||||
for user in server_users:
|
||||
websocket_helper.broadcast_user(user, 'notification', "Executable download finished")
|
||||
time.sleep(3)
|
||||
websocket_helper.broadcast_user(user, 'send_start_reload', {
|
||||
})
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Unable to save jar to {path} due to error:{error}".format(path=path, error=e))
|
||||
pass
|
||||
websocket_helper.broadcast('notification', "Executable download finished for server named: " + name)
|
||||
|
||||
logger.error(f"Unable to save jar to {path} due to error:{e}")
|
||||
Servers_Controller.finish_download(server_id)
|
||||
server_users = server_permissions.get_server_user_list(server_id)
|
||||
for user in server_users:
|
||||
websocket_helper.broadcast_user(user, 'notification', "Executable download finished")
|
||||
time.sleep(3)
|
||||
websocket_helper.broadcast_user(user, 'send_start_reload', {
|
||||
})
|
||||
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
server_jar_obj = ServerJars()
|
||||
|
@ -1,20 +1,16 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import psutil
|
||||
import logging
|
||||
import datetime
|
||||
import base64
|
||||
import psutil
|
||||
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.minecraft.mc_ping import ping
|
||||
from app.classes.models.management import Host_Stats
|
||||
from app.classes.models.servers import Server_Stats, servers_helper
|
||||
from app.classes.models.servers import servers_helper
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Stats:
|
||||
|
||||
def __init__(self, controller):
|
||||
@ -38,8 +34,8 @@ class Stats:
|
||||
'mem_total': helper.human_readable_file_size(psutil.virtual_memory()[0]),
|
||||
'disk_data': self._all_disk_usage()
|
||||
}
|
||||
server_stats = self.get_servers_stats()
|
||||
data['servers'] = server_stats
|
||||
#server_stats = self.get_servers_stats()
|
||||
#data['servers'] = server_stats
|
||||
data['node_stats'] = node_stats
|
||||
|
||||
return data
|
||||
@ -64,8 +60,6 @@ class Stats:
|
||||
|
||||
real_cpu = round(p.cpu_percent(interval=0.5) / psutil.cpu_count(), 2)
|
||||
|
||||
process_start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.create_time()))
|
||||
|
||||
# this is a faster way of getting data for a process
|
||||
with p.oneshot():
|
||||
process_stats = {
|
||||
@ -76,7 +70,7 @@ class Stats:
|
||||
return process_stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to get process details for pid: {} due to error: {}".format(process_pid, e))
|
||||
logger.error(f"Unable to get process details for pid: {process_pid} due to error: {e}")
|
||||
|
||||
# Dummy Data
|
||||
process_stats = {
|
||||
@ -92,7 +86,7 @@ class Stats:
|
||||
# print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type","Mount"))
|
||||
|
||||
for part in psutil.disk_partitions(all=False):
|
||||
if os.name == 'nt':
|
||||
if helper.is_os_windows():
|
||||
if 'cdrom' in part.opts or part.fstype == '':
|
||||
# skip cd-rom drives with no disk in it; they may raise
|
||||
# ENOENT, pop-up a Windows GUI error for a non-ready
|
||||
@ -114,28 +108,44 @@ class Stats:
|
||||
return disk_data
|
||||
|
||||
@staticmethod
|
||||
def get_world_size(world_path):
|
||||
def get_world_size(server_path):
|
||||
|
||||
total_size = 0
|
||||
|
||||
# do a scan of the directories in the server path.
|
||||
for root, dirs, files in os.walk(world_path, topdown=False):
|
||||
|
||||
# for each directory we find
|
||||
for name in dirs:
|
||||
|
||||
# if the directory name is "region"
|
||||
if name == "region":
|
||||
# log it!
|
||||
logger.debug("Path %s is called region. Getting directory size", os.path.join(root, name))
|
||||
|
||||
# get this directory size, and add it to the total we have running.
|
||||
total_size += helper.get_dir_size(os.path.join(root, name))
|
||||
total_size = helper.get_dir_size(server_path)
|
||||
|
||||
level_total_size = helper.human_readable_file_size(total_size)
|
||||
|
||||
return level_total_size
|
||||
|
||||
def get_server_players(self, server_id):
|
||||
|
||||
server = servers_helper.get_server_data_by_id(server_id)
|
||||
|
||||
logger.info(f"Getting players for server {server}")
|
||||
|
||||
# get our settings and data dictionaries
|
||||
# server_settings = server.get('server_settings', {})
|
||||
# server_data = server.get('server_data_obj', {})
|
||||
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
internal_ip = server['server_ip']
|
||||
server_port = server['server_port']
|
||||
|
||||
logger.debug("Pinging {internal_ip} on port {server_port}")
|
||||
if servers_helper.get_server_type_by_id(server_id) != 'minecraft-bedrock':
|
||||
int_mc_ping = ping(internal_ip, int(server_port))
|
||||
|
||||
|
||||
ping_data = {}
|
||||
|
||||
# if we got a good ping return, let's parse it
|
||||
if int_mc_ping:
|
||||
ping_data = Stats.parse_server_ping(int_mc_ping)
|
||||
return ping_data['players']
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def parse_server_ping(ping_obj: object):
|
||||
online_stats = {}
|
||||
@ -144,14 +154,15 @@ class Stats:
|
||||
online_stats = json.loads(ping_obj.players)
|
||||
|
||||
except Exception as e:
|
||||
logger.info("Unable to read json from ping_obj: {}".format(e))
|
||||
pass
|
||||
logger.info(f"Unable to read json from ping_obj: {e}")
|
||||
|
||||
|
||||
try:
|
||||
server_icon = base64.encodebytes(ping_obj.icon)
|
||||
server_icon = server_icon.decode('utf-8')
|
||||
except Exception as e:
|
||||
server_icon = False
|
||||
logger.info("Unable to read the server icon : {}".format(e))
|
||||
logger.info(f"Unable to read the server icon : {e}")
|
||||
|
||||
ping_data = {
|
||||
'online': online_stats.get("online", 0),
|
||||
@ -163,159 +174,27 @@ class Stats:
|
||||
}
|
||||
|
||||
return ping_data
|
||||
|
||||
def get_server_players(self, server_id):
|
||||
|
||||
server = servers_helper.get_server_data_by_id(server_id)
|
||||
@staticmethod
|
||||
def parse_server_RakNet_ping(ping_obj: object):
|
||||
|
||||
logger.info("Getting players for server {}".format(server))
|
||||
|
||||
# get our settings and data dictionaries
|
||||
server_settings = server.get('server_settings', {})
|
||||
server_data = server.get('server_data_obj', {})
|
||||
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
internal_ip = server['server_ip']
|
||||
server_port = server['server_port']
|
||||
|
||||
logger.debug("Pinging {} on port {}".format(internal_ip, server_port))
|
||||
int_mc_ping = ping(internal_ip, int(server_port))
|
||||
|
||||
ping_data = {}
|
||||
|
||||
# if we got a good ping return, let's parse it
|
||||
if int_mc_ping:
|
||||
ping_data = self.parse_server_ping(int_mc_ping)
|
||||
return ping_data['players']
|
||||
return []
|
||||
|
||||
def get_servers_stats(self):
|
||||
|
||||
server_stats_list = []
|
||||
server_stats = {}
|
||||
|
||||
servers = self.controller.servers_list
|
||||
|
||||
logger.info("Getting Stats for all servers...")
|
||||
|
||||
for s in servers:
|
||||
|
||||
server_id = s.get('server_id', None)
|
||||
server = servers_helper.get_server_data_by_id(server_id)
|
||||
|
||||
|
||||
logger.debug('Getting stats for server: {}'.format(server_id))
|
||||
|
||||
# get our server object, settings and data dictionaries
|
||||
server_obj = s.get('server_obj', None)
|
||||
server_obj.reload_server_settings()
|
||||
server_settings = s.get('server_settings', {})
|
||||
server_data = self.controller.get_server_data(server_id)
|
||||
|
||||
# world data
|
||||
world_name = server_settings.get('level-name', 'Unknown')
|
||||
world_path = os.path.join(server_data.get('path', None), world_name)
|
||||
|
||||
# process stats
|
||||
p_stats = self._get_process_stats(server_obj.process)
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
internal_ip = server['server_ip']
|
||||
server_port = server['server_port']
|
||||
|
||||
logger.debug("Pinging server '{}' on {}:{}".format(s.get('server_name', "ID#{}".format(server_id)), internal_ip, server_port))
|
||||
int_mc_ping = ping(internal_ip, int(server_port))
|
||||
|
||||
int_data = False
|
||||
ping_data = {}
|
||||
|
||||
# if we got a good ping return, let's parse it
|
||||
if int_mc_ping:
|
||||
int_data = True
|
||||
ping_data = self.parse_server_ping(int_mc_ping)
|
||||
|
||||
server_stats = {
|
||||
'id': server_id,
|
||||
'started': server_obj.get_start_time(),
|
||||
'running': server_obj.check_running(),
|
||||
'cpu': p_stats.get('cpu_usage', 0),
|
||||
'mem': p_stats.get('memory_usage', 0),
|
||||
"mem_percent": p_stats.get('mem_percentage', 0),
|
||||
'world_name': world_name,
|
||||
'world_size': self.get_world_size(world_path),
|
||||
'server_port': server_port,
|
||||
'int_ping_results': int_data,
|
||||
'online': ping_data.get("online", False),
|
||||
"max": ping_data.get("max", False),
|
||||
'players': ping_data.get("players", False),
|
||||
'desc': ping_data.get("server_description", False),
|
||||
'version': ping_data.get("server_version", False)
|
||||
}
|
||||
|
||||
# add this servers data to the stack
|
||||
server_stats_list.append(server_stats)
|
||||
|
||||
return server_stats_list
|
||||
|
||||
def get_raw_server_stats(self, server_id):
|
||||
|
||||
server_stats = {}
|
||||
server = self.controller.get_server_obj(server_id)
|
||||
|
||||
logger.debug('Getting stats for server: {}'.format(server_id))
|
||||
|
||||
# get our server object, settings and data dictionaries
|
||||
server_obj = self.controller.get_server_obj(server_id)
|
||||
server_obj.reload_server_settings()
|
||||
server_settings = self.controller.get_server_settings(server_id)
|
||||
server_data = self.controller.get_server_data(server_id)
|
||||
|
||||
# world data
|
||||
world_name = server_settings.get('level-name', 'Unknown')
|
||||
world_path = os.path.join(server_data.get('path', None), world_name)
|
||||
|
||||
# process stats
|
||||
p_stats = self._get_process_stats(server_obj.process)
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
#internal_ip = server['server_ip']
|
||||
#server_port = server['server_port']
|
||||
internal_ip = server_data.get('server_ip', "127.0.0.1")
|
||||
server_port = server_settings.get('server-port', "25565")
|
||||
|
||||
|
||||
logger.debug("Pinging server '{}' on {}:{}".format(server.name, internal_ip, server_port))
|
||||
int_mc_ping = ping(internal_ip, int(server_port))
|
||||
|
||||
int_data = False
|
||||
ping_data = {}
|
||||
|
||||
# if we got a good ping return, let's parse it
|
||||
if int_mc_ping:
|
||||
int_data = True
|
||||
ping_data = self.parse_server_ping(int_mc_ping)
|
||||
|
||||
server_stats = {
|
||||
'id': server_id,
|
||||
'started': server_obj.get_start_time(),
|
||||
'running': server_obj.check_running(),
|
||||
'cpu': p_stats.get('cpu_usage', 0),
|
||||
'mem': p_stats.get('memory_usage', 0),
|
||||
"mem_percent": p_stats.get('mem_percentage', 0),
|
||||
'world_name': world_name,
|
||||
'world_size': self.get_world_size(world_path),
|
||||
'server_port': server_port,
|
||||
'int_ping_results': int_data,
|
||||
'online': ping_data.get("online", False),
|
||||
"max": ping_data.get("max", False),
|
||||
'players': ping_data.get("players", False),
|
||||
'desc': ping_data.get("server_description", False),
|
||||
'version': ping_data.get("server_version", False),
|
||||
'icon': ping_data.get("server_icon", False)
|
||||
try:
|
||||
server_icon = base64.encodebytes(ping_obj['icon'])
|
||||
except Exception as e:
|
||||
server_icon = False
|
||||
logger.info(f"Unable to read the server icon : {e}")
|
||||
ping_data = {
|
||||
'online': ping_obj['server_player_count'],
|
||||
'max': ping_obj['server_player_max'],
|
||||
'players': [],
|
||||
'server_description': ping_obj['server_edition'],
|
||||
'server_version': ping_obj['server_version_name'],
|
||||
'server_icon': server_icon
|
||||
}
|
||||
|
||||
return server_stats
|
||||
|
||||
return ping_data
|
||||
|
||||
|
||||
def record_stats(self):
|
||||
stats_to_send = self.get_node_stats()
|
||||
@ -333,26 +212,26 @@ class Stats:
|
||||
Host_Stats.disk_json: node_stats.get('disk_data', '{}')
|
||||
}).execute()
|
||||
|
||||
server_stats = stats_to_send.get('servers')
|
||||
|
||||
for server in server_stats:
|
||||
Server_Stats.insert({
|
||||
Server_Stats.server_id: server.get('id', 0),
|
||||
Server_Stats.started: server.get('started', ""),
|
||||
Server_Stats.running: server.get('running', False),
|
||||
Server_Stats.cpu: server.get('cpu', 0),
|
||||
Server_Stats.mem: server.get('mem', 0),
|
||||
Server_Stats.mem_percent: server.get('mem_percent', 0),
|
||||
Server_Stats.world_name: server.get('world_name', ""),
|
||||
Server_Stats.world_size: server.get('world_size', ""),
|
||||
Server_Stats.server_port: server.get('server_port', ""),
|
||||
Server_Stats.int_ping_results: server.get('int_ping_results', False),
|
||||
Server_Stats.online: server.get("online", False),
|
||||
Server_Stats.max: server.get("max", False),
|
||||
Server_Stats.players: server.get("players", False),
|
||||
Server_Stats.desc: server.get("desc", False),
|
||||
Server_Stats.version: server.get("version", False)
|
||||
}).execute()
|
||||
# server_stats = stats_to_send.get('servers')#
|
||||
#
|
||||
# for server in server_stats:
|
||||
# Server_Stats.insert({
|
||||
# Server_Stats.server_id: server.get('id', 0),
|
||||
# Server_Stats.started: server.get('started', ""),
|
||||
# Server_Stats.running: server.get('running', False),
|
||||
# Server_Stats.cpu: server.get('cpu', 0),
|
||||
# Server_Stats.mem: server.get('mem', 0),
|
||||
# Server_Stats.mem_percent: server.get('mem_percent', 0),
|
||||
# Server_Stats.world_name: server.get('world_name', ""),
|
||||
# Server_Stats.world_size: server.get('world_size', ""),
|
||||
# Server_Stats.server_port: server.get('server_port', ""),
|
||||
# Server_Stats.int_ping_results: server.get('int_ping_results', False),
|
||||
# Server_Stats.online: server.get("online", False),
|
||||
# Server_Stats.max: server.get("max", False),
|
||||
# Server_Stats.players: server.get("players", False),
|
||||
# Server_Stats.desc: server.get("desc", False),
|
||||
# Server_Stats.version: server.get("version", False)
|
||||
# }).execute()
|
||||
|
||||
# delete old data
|
||||
max_age = helper.get_setting("history_max_age")
|
||||
@ -360,4 +239,4 @@ class Stats:
|
||||
last_week = now.day - max_age
|
||||
|
||||
Host_Stats.delete().where(Host_Stats.time < last_week).execute()
|
||||
Server_Stats.delete().where(Server_Stats.created < last_week).execute()
|
||||
# Server_Stats.delete().where(Server_Stats.created < last_week).execute()
|
||||
|
@ -1,28 +1,20 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.users import Users
|
||||
from app.classes.shared.permission_helper import permission_helper
|
||||
from app.classes.models.users import Users, ApiKeys
|
||||
|
||||
try:
|
||||
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, IntegerField, DoesNotExist
|
||||
from enum import Enum
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
database = SqliteDatabase(helper.db_path, pragmas = {
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
@ -123,7 +115,7 @@ class Permissions_Crafty:
|
||||
def get_User_Crafty(user_id):
|
||||
try:
|
||||
user_crafty = User_Crafty.select().where(User_Crafty.user_id == user_id).get()
|
||||
except User_Crafty.DoesNotExist:
|
||||
except DoesNotExist:
|
||||
user_crafty = User_Crafty.insert({
|
||||
User_Crafty.user_id: user_id,
|
||||
User_Crafty.permissions: "000",
|
||||
@ -172,7 +164,6 @@ class Permissions_Crafty:
|
||||
|
||||
@staticmethod
|
||||
def get_crafty_limit_value(user_id, permission):
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
quantity_list = crafty_permissions.get_permission_quantity_list(user_id)
|
||||
return quantity_list[permission]
|
||||
|
||||
@ -191,4 +182,18 @@ class Permissions_Crafty:
|
||||
User_Crafty.save(user_crafty)
|
||||
return user_crafty.created_server
|
||||
|
||||
crafty_permissions = Permissions_Crafty()
|
||||
@staticmethod
|
||||
def get_api_key_permissions_list(key: ApiKeys):
|
||||
user = key.user
|
||||
if user.superuser and key.superuser:
|
||||
return crafty_permissions.get_permissions_list()
|
||||
else:
|
||||
user_permissions_mask = crafty_permissions.get_crafty_permissions_mask(user.user_id)
|
||||
key_permissions_mask: str = key.crafty_permissions
|
||||
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
|
||||
permissions_list = crafty_permissions.get_permissions(permissions_mask)
|
||||
return permissions_list
|
||||
|
||||
|
||||
|
||||
crafty_permissions = Permissions_Crafty()
|
||||
|
@ -1,32 +1,24 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import db_helper
|
||||
from app.classes.models.users import Users, users_helper
|
||||
from app.classes.models.servers import Servers, servers_helper
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.server_permissions import server_permissions
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.main_models import db_helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
try:
|
||||
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, IntegerField, DateTimeField, FloatField, TextField, AutoField, BooleanField
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
database = SqliteDatabase(helper.db_path, pragmas = {
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
@ -112,6 +104,10 @@ class Schedules(Model):
|
||||
start_time = CharField(null=True)
|
||||
command = CharField(null=True)
|
||||
comment = CharField()
|
||||
one_time = BooleanField(default=False)
|
||||
cron_string = CharField(default="")
|
||||
parent = IntegerField(null=True)
|
||||
delay = IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
table_name = 'schedules'
|
||||
@ -122,22 +118,22 @@ class Schedules(Model):
|
||||
# Backups Class
|
||||
#************************************************************************************************
|
||||
class Backups(Model):
|
||||
directories = CharField(null=True)
|
||||
excluded_dirs = CharField(null=True)
|
||||
max_backups = IntegerField()
|
||||
server_id = ForeignKeyField(Servers, backref='backups_server')
|
||||
schedule_id = ForeignKeyField(Schedules, backref='backups_schedule')
|
||||
|
||||
compress = BooleanField(default=False)
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
database = database
|
||||
|
||||
class helpers_management:
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Host_Stats Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_latest_hosts_stats():
|
||||
#pylint: disable=no-member
|
||||
query = Host_Stats.select().order_by(Host_Stats.id.desc()).get()
|
||||
return model_to_dict(query)
|
||||
|
||||
@ -156,16 +152,16 @@ class helpers_management:
|
||||
@staticmethod
|
||||
def get_unactioned_commands():
|
||||
query = Commands.select().where(Commands.executed == 0)
|
||||
return db_helper.return_rows(query)
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
def mark_command_complete(command_id=None):
|
||||
if command_id is not None:
|
||||
logger.debug("Marking Command {} completed".format(command_id))
|
||||
logger.debug(f"Marking Command {command_id} completed")
|
||||
Commands.update({
|
||||
Commands.executed: True
|
||||
}).where(Commands.command_id == command_id).execute()
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Audit_Log Methods
|
||||
#************************************************************************************************
|
||||
@ -173,15 +169,17 @@ class helpers_management:
|
||||
def get_actity_log():
|
||||
q = Audit_Log.select()
|
||||
return db_helper.return_db_rows(q)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log(user_id, log_msg, server_id=None, source_ip=None):
|
||||
logger.debug("Adding to audit log User:{} - Message: {} ".format(user_id, log_msg))
|
||||
logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ")
|
||||
user_data = users_helper.get_user(user_id)
|
||||
|
||||
audit_msg = "{} {}".format(str(user_data['username']).capitalize(), log_msg)
|
||||
audit_msg = f"{str(user_data['username']).capitalize()} {log_msg}"
|
||||
|
||||
websocket_helper.broadcast('notification', audit_msg)
|
||||
server_users = server_permissions.get_server_user_list(server_id)
|
||||
for user in server_users:
|
||||
websocket_helper.broadcast_user(user,'notification', audit_msg)
|
||||
|
||||
Audit_Log.insert({
|
||||
Audit_Log.user_name: user_data['username'],
|
||||
@ -190,6 +188,17 @@ class helpers_management:
|
||||
Audit_Log.log_msg: audit_msg,
|
||||
Audit_Log.source_ip: source_ip
|
||||
}).execute()
|
||||
#deletes records when they're more than 100
|
||||
ordered = Audit_Log.select().order_by(+Audit_Log.created)
|
||||
for item in ordered:
|
||||
if not helper.get_setting('max_audit_entries'):
|
||||
max_entries = 300
|
||||
else:
|
||||
max_entries = helper.get_setting('max_audit_entries')
|
||||
if Audit_Log.select().count() > max_entries:
|
||||
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute()
|
||||
else:
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip):
|
||||
@ -200,12 +209,35 @@ class helpers_management:
|
||||
Audit_Log.log_msg: log_msg,
|
||||
Audit_Log.source_ip: source_ip
|
||||
}).execute()
|
||||
|
||||
#deletes records when they're more than 100
|
||||
ordered = Audit_Log.select().order_by(+Audit_Log.created)
|
||||
for item in ordered:
|
||||
#configurable through app/config/config.json
|
||||
if not helper.get_setting('max_audit_entries'):
|
||||
max_entries = 300
|
||||
else:
|
||||
max_entries = helper.get_setting('max_audit_entries')
|
||||
if Audit_Log.select().count() > max_entries:
|
||||
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute()
|
||||
else:
|
||||
return
|
||||
#************************************************************************************************
|
||||
# Schedules Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True):
|
||||
def create_scheduled_task(
|
||||
server_id,
|
||||
action,
|
||||
interval,
|
||||
interval_type,
|
||||
start_time,
|
||||
command,
|
||||
comment=None,
|
||||
enabled=True,
|
||||
one_time=False,
|
||||
cron_string='* * * * *',
|
||||
parent=None,
|
||||
delay=0):
|
||||
sch_id = Schedules.insert({
|
||||
Schedules.server_id: server_id,
|
||||
Schedules.action: action,
|
||||
@ -214,7 +246,12 @@ class helpers_management:
|
||||
Schedules.interval_type: interval_type,
|
||||
Schedules.start_time: start_time,
|
||||
Schedules.command: command,
|
||||
Schedules.comment: comment
|
||||
Schedules.comment: comment,
|
||||
Schedules.one_time: one_time,
|
||||
Schedules.cron_string: cron_string,
|
||||
Schedules.parent: parent,
|
||||
Schedules.delay: delay
|
||||
|
||||
}).execute()
|
||||
return sch_id
|
||||
|
||||
@ -227,20 +264,37 @@ class helpers_management:
|
||||
def update_scheduled_task(schedule_id, updates):
|
||||
Schedules.update(updates).where(Schedules.schedule_id == schedule_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def delete_scheduled_task_by_server(server_id):
|
||||
Schedules.delete().where(Schedules.server_id == int(server_id)).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task(schedule_id):
|
||||
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute()
|
||||
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id))
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task_model(schedule_id):
|
||||
return Schedules.select().where(Schedules.schedule_id == schedule_id).get()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_by_server(server_id):
|
||||
return Schedules.select().where(Schedules.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_child_schedules_by_server(schedule_id, server_id):
|
||||
return Schedules.select().where(Schedules.server_id == server_id, Schedules.parent == schedule_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_child_schedules(schedule_id):
|
||||
return Schedules.select().where(Schedules.parent == schedule_id)
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_all():
|
||||
return Schedules.select().execute()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_enabled():
|
||||
#pylint: disable=singleton-comparison
|
||||
return Schedules.select().where(Schedules.enabled == True).execute()
|
||||
|
||||
#************************************************************************************************
|
||||
@ -249,51 +303,44 @@ class helpers_management:
|
||||
@staticmethod
|
||||
def get_backup_config(server_id):
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Servers)[0]
|
||||
conf = {
|
||||
"backup_path": row.server_id.backup_path,
|
||||
"directories": row.directories,
|
||||
"excluded_dirs": row.excluded_dirs,
|
||||
"max_backups": row.max_backups,
|
||||
"auto_enabled": row.schedule_id.enabled,
|
||||
"server_id": row.server_id.server_id
|
||||
"server_id": row.server_id.server_id,
|
||||
"compress": row.compress
|
||||
}
|
||||
except IndexError:
|
||||
conf = {
|
||||
"backup_path": None,
|
||||
"directories": None,
|
||||
"excluded_dirs": None,
|
||||
"max_backups": 0,
|
||||
"auto_enabled": True,
|
||||
"server_id": server_id
|
||||
"server_id": server_id,
|
||||
"compress": False,
|
||||
}
|
||||
return conf
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
logger.debug("Updating server {} backup config with {}".format(server_id, locals()))
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None, compress: bool = False):
|
||||
logger.debug(f"Updating server {server_id} backup config with {locals()}")
|
||||
if Backups.select().where(Backups.server_id == server_id).count() != 0:
|
||||
new_row = False
|
||||
conf = {}
|
||||
schd = {}
|
||||
except IndexError:
|
||||
else:
|
||||
conf = {
|
||||
"directories": None,
|
||||
"excluded_dirs": None,
|
||||
"max_backups": 0,
|
||||
"server_id": server_id
|
||||
}
|
||||
schd = {
|
||||
"enabled": True,
|
||||
"action": "backup_server",
|
||||
"interval_type": "days",
|
||||
"interval": 1,
|
||||
"start_time": "00:00",
|
||||
"server_id": server_id,
|
||||
"comment": "Default backup job"
|
||||
"server_id": server_id,
|
||||
"compress": False
|
||||
}
|
||||
new_row = True
|
||||
if max_backups is not None:
|
||||
conf['max_backups'] = max_backups
|
||||
schd['enabled'] = bool(auto_enabled)
|
||||
if excluded_dirs is not None:
|
||||
dirs_to_exclude = ",".join(excluded_dirs)
|
||||
conf['excluded_dirs'] = dirs_to_exclude
|
||||
conf['compress'] = compress
|
||||
if not new_row:
|
||||
with database.atomic():
|
||||
if backup_path is not None:
|
||||
@ -301,17 +348,47 @@ class helpers_management:
|
||||
else:
|
||||
u1 = 0
|
||||
u2 = Backups.update(conf).where(Backups.server_id == server_id).execute()
|
||||
u3 = Schedules.update(schd).where(Schedules.schedule_id == row.schedule_id).execute()
|
||||
logger.debug("Updating existing backup record. {}+{}+{} rows affected".format(u1, u2, u3))
|
||||
logger.debug(f"Updating existing backup record. {u1}+{u2} rows affected")
|
||||
else:
|
||||
with database.atomic():
|
||||
conf["server_id"] = server_id
|
||||
if backup_path is not None:
|
||||
u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
|
||||
s = Schedules.create(**schd)
|
||||
conf['schedule_id'] = s.schedule_id
|
||||
b = Backups.create(**conf)
|
||||
Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
|
||||
Backups.create(**conf)
|
||||
logger.debug("Creating new backup record.")
|
||||
|
||||
def get_excluded_backup_dirs(self, server_id: int):
|
||||
excluded_dirs = self.get_backup_config(server_id)['excluded_dirs']
|
||||
if excluded_dirs is not None and excluded_dirs != "":
|
||||
dir_list = excluded_dirs.split(",")
|
||||
else:
|
||||
dir_list = []
|
||||
return dir_list
|
||||
|
||||
def add_excluded_backup_dir(self, server_id: int, dir_to_add: str):
|
||||
dir_list = self.get_excluded_backup_dirs()
|
||||
if dir_to_add not in dir_list:
|
||||
dir_list.append(dir_to_add)
|
||||
excluded_dirs = ",".join(dir_list)
|
||||
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
|
||||
else:
|
||||
logger.debug(f"Not adding {dir_to_add} to excluded directories - already in the excluded directory list for server ID {server_id}")
|
||||
|
||||
def del_excluded_backup_dir(self, server_id: int, dir_to_del: str):
|
||||
dir_list = self.get_excluded_backup_dirs()
|
||||
if dir_to_del in dir_list:
|
||||
dir_list.remove(dir_to_del)
|
||||
excluded_dirs = ",".join(dir_list)
|
||||
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
|
||||
else:
|
||||
logger.debug(f"Not removing {dir_to_del} from excluded directories - not in the excluded directory list for server ID {server_id}")
|
||||
|
||||
@staticmethod
|
||||
def clear_unexecuted_commands():
|
||||
Commands.update({
|
||||
Commands.executed: True
|
||||
#pylint: disable=singleton-comparison
|
||||
}).where(Commands.executed == False).execute()
|
||||
|
||||
|
||||
management_helper = helpers_management()
|
||||
|
@ -1,27 +1,19 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
try:
|
||||
from peewee import SqliteDatabase, Model, CharField, DoesNotExist, AutoField, DateTimeField
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
database = SqliteDatabase(helper.db_path, pragmas = {
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
@ -45,7 +37,7 @@ class helper_roles:
|
||||
@staticmethod
|
||||
def get_all_roles():
|
||||
query = Roles.select()
|
||||
return query
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
def get_roleid_by_name(role_name):
|
||||
@ -81,5 +73,5 @@ class helper_roles:
|
||||
if not roles_helper.get_role(role_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
roles_helper = helper_roles()
|
||||
|
@ -1,34 +1,25 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.roles import Roles
|
||||
from app.classes.models.users import users_helper
|
||||
from app.classes.models.users import User_Roles, users_helper, ApiKeys, Users
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.permission_helper import permission_helper
|
||||
|
||||
try:
|
||||
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, CompositeKey, JOIN
|
||||
from enum import Enum
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
database = SqliteDatabase(helper.db_path, pragmas = {
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Role Servers Class
|
||||
#************************************************************************************************
|
||||
@ -78,10 +69,7 @@ class Permissions_Servers:
|
||||
|
||||
@staticmethod
|
||||
def has_permission(permission_mask, permission_tested: Enum_Permissions_Server):
|
||||
result = False
|
||||
if permission_mask[permission_tested.value] == '1':
|
||||
result = True
|
||||
return result
|
||||
return permission_mask[permission_tested.value] == '1'
|
||||
|
||||
@staticmethod
|
||||
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value):
|
||||
@ -94,6 +82,14 @@ class Permissions_Servers:
|
||||
def get_permission(permission_mask, permission_tested: Enum_Permissions_Server):
|
||||
return permission_mask[permission_tested.value]
|
||||
|
||||
@staticmethod
|
||||
def get_token_permissions(permissions_mask, api_permissions_mask):
|
||||
permissions_list = []
|
||||
for member in Enum_Permissions_Server.__members__.items():
|
||||
if permission_helper.both_have_perm(permissions_mask, api_permissions_mask, member[1]):
|
||||
permissions_list.append(member[1])
|
||||
return permissions_list
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Role_Servers Methods
|
||||
@ -112,20 +108,29 @@ class Permissions_Servers:
|
||||
|
||||
@staticmethod
|
||||
def add_role_server(server_id, role_id, rs_permissions="00000000"):
|
||||
servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id, Role_Servers.permissions: rs_permissions}).execute()
|
||||
servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id,
|
||||
Role_Servers.permissions: rs_permissions}).execute()
|
||||
return servers
|
||||
|
||||
@staticmethod
|
||||
def get_permissions_mask(role_id, server_id):
|
||||
permissions_mask = ''
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).execute()
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).get()
|
||||
permissions_mask = role_server.permissions
|
||||
return permissions_mask
|
||||
|
||||
@staticmethod
|
||||
def get_server_roles(server_id):
|
||||
role_list = []
|
||||
roles = Role_Servers.select().where(Role_Servers.server_id == server_id).execute()
|
||||
for role in roles:
|
||||
role_list.append(role.role_id)
|
||||
return role_list
|
||||
|
||||
@staticmethod
|
||||
def get_role_permissions_list(role_id):
|
||||
permissions_mask = '00000000'
|
||||
role_server = Role_Servers.get_or_none(role_id)
|
||||
role_server = Role_Servers.get_or_none(Role_Servers.role_id == role_id)
|
||||
if role_server is not None:
|
||||
permissions_mask = role_server.permissions
|
||||
permissions_list = server_permissions.get_permissions(permissions_mask)
|
||||
@ -138,7 +143,9 @@ class Permissions_Servers:
|
||||
Role_Servers.save(role_server)
|
||||
|
||||
@staticmethod
|
||||
def delete_roles_permissions(role_id, removed_servers={}):
|
||||
def delete_roles_permissions(role_id, removed_servers=None):
|
||||
if removed_servers is None:
|
||||
removed_servers = {}
|
||||
return Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute()
|
||||
|
||||
@staticmethod
|
||||
@ -147,18 +154,71 @@ class Permissions_Servers:
|
||||
return Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_user_permissions_list(user_id, server_id):
|
||||
permissions_mask = ''
|
||||
permissions_list = []
|
||||
def get_user_id_permissions_mask(user_id, server_id: str):
|
||||
user = users_helper.get_user_model(user_id)
|
||||
return server_permissions.get_user_permissions_mask(user, server_id)
|
||||
|
||||
user = users_helper.get_user(user_id)
|
||||
if user['superuser'] == True:
|
||||
@staticmethod
|
||||
def get_user_permissions_mask(user: Users, server_id: str):
|
||||
if user.superuser:
|
||||
permissions_mask = '1' * len(server_permissions.get_permissions_list())
|
||||
else:
|
||||
roles_list = users_helper.get_user_roles_id(user.user_id)
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
|
||||
try:
|
||||
permissions_mask = role_server[0].permissions
|
||||
except IndexError:
|
||||
permissions_mask = '0' * len(server_permissions.get_permissions_list())
|
||||
return permissions_mask
|
||||
|
||||
@staticmethod
|
||||
def get_server_user_list(server_id):
|
||||
final_users = []
|
||||
server_roles = Role_Servers.select().where(Role_Servers.server_id == server_id)
|
||||
# pylint: disable=singleton-comparison
|
||||
super_users = Users.select().where(Users.superuser == True)
|
||||
for role in server_roles:
|
||||
users = User_Roles.select().where(User_Roles.role_id == role.role_id)
|
||||
for user in users:
|
||||
if user.user_id.user_id not in final_users:
|
||||
final_users.append(user.user_id.user_id)
|
||||
for suser in super_users:
|
||||
if suser.user_id not in final_users:
|
||||
final_users.append(suser.user_id)
|
||||
return final_users
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_permissions_list(user_id, server_id: str):
|
||||
user = users_helper.get_user_model(user_id)
|
||||
return server_permissions.get_user_permissions_list(user, server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_user_permissions_list(user: Users, server_id: str):
|
||||
if user.superuser:
|
||||
permissions_list = server_permissions.get_permissions_list()
|
||||
else:
|
||||
roles_list = users_helper.get_user_roles_id(user_id)
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == int(server_id)).execute()
|
||||
permissions_mask = role_server[0].permissions
|
||||
permissions_mask = server_permissions.get_user_permissions_mask(user, server_id)
|
||||
permissions_list = server_permissions.get_permissions(permissions_mask)
|
||||
return permissions_list
|
||||
|
||||
server_permissions = Permissions_Servers()
|
||||
@staticmethod
|
||||
def get_api_key_id_permissions_list(key_id, server_id: str):
|
||||
key = ApiKeys.get(ApiKeys.token_id == key_id)
|
||||
return server_permissions.get_api_key_permissions_list(key, server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
|
||||
user = key.user
|
||||
if user.superuser and key.superuser:
|
||||
return server_permissions.get_permissions_list()
|
||||
else:
|
||||
roles_list = users_helper.get_user_roles_id(user['user_id'])
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
|
||||
user_permissions_mask = role_server[0].permissions
|
||||
key_permissions_mask = key.server_permissions
|
||||
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
|
||||
permissions_list = server_permissions.get_permissions(permissions_mask)
|
||||
return permissions_list
|
||||
|
||||
|
||||
server_permissions = Permissions_Servers()
|
||||
|
@ -1,29 +1,19 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.main_models import db_helper
|
||||
|
||||
try:
|
||||
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, AutoField, DateTimeField, BooleanField, IntegerField, FloatField
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
database = SqliteDatabase(helper.db_path, pragmas = {
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
@ -48,6 +38,7 @@ class Servers(Model):
|
||||
server_ip = CharField(default="127.0.0.1")
|
||||
server_port = IntegerField(default=25565)
|
||||
logs_delete_after = IntegerField(default=0)
|
||||
type = CharField(default="minecraft-java")
|
||||
|
||||
class Meta:
|
||||
table_name = "servers"
|
||||
@ -77,6 +68,9 @@ class Server_Stats(Model):
|
||||
version = CharField(default="")
|
||||
updating = BooleanField(default=False)
|
||||
waiting_start = BooleanField(default=False)
|
||||
first_run = BooleanField(default=True)
|
||||
crashed = BooleanField(default=False)
|
||||
downloading = BooleanField(default=False)
|
||||
|
||||
|
||||
class Meta:
|
||||
@ -88,12 +82,22 @@ class Server_Stats(Model):
|
||||
# Servers Class
|
||||
#************************************************************************************************
|
||||
class helper_servers:
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Generic Servers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
def create_server(
|
||||
name: str,
|
||||
server_uuid: str,
|
||||
server_dir: str,
|
||||
backup_path: str,
|
||||
server_command: str,
|
||||
server_file: str,
|
||||
server_log_file: str,
|
||||
server_stop: str,
|
||||
server_type: str,
|
||||
server_port=25565):
|
||||
return Servers.insert({
|
||||
Servers.server_name: name,
|
||||
Servers.server_uuid: server_uuid,
|
||||
@ -106,9 +110,24 @@ class helper_servers:
|
||||
Servers.log_path: server_log_file,
|
||||
Servers.server_port: server_port,
|
||||
Servers.stop_command: server_stop,
|
||||
Servers.backup_path: backup_path
|
||||
Servers.backup_path: backup_path,
|
||||
Servers.type: server_type
|
||||
}).execute()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_server_obj(server_id):
|
||||
return Servers.get_by_id(server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_server_type_by_id(server_id):
|
||||
server_type = Servers.select().where(Servers.server_id == server_id).get()
|
||||
return server_type.type
|
||||
|
||||
@staticmethod
|
||||
def update_server(server_obj):
|
||||
return server_obj.save()
|
||||
|
||||
@staticmethod
|
||||
def remove_server(server_id):
|
||||
with database.atomic():
|
||||
@ -134,16 +153,18 @@ class helper_servers:
|
||||
def get_all_servers_stats():
|
||||
servers = servers_helper.get_all_defined_servers()
|
||||
server_data = []
|
||||
|
||||
for s in servers:
|
||||
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":True})
|
||||
try:
|
||||
for s in servers:
|
||||
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":True})
|
||||
except IndexError as ex:
|
||||
logger.error(f"Stats collection failed with error: {ex}. Was a server just created?")
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_server_friendly_name(server_id):
|
||||
server_data = servers_helper.get_server_data_by_id(server_id)
|
||||
friendly_name = "{} with ID: {}".format(server_data.get('server_name', None), server_data.get('server_id', 0))
|
||||
friendly_name = f"{server_data.get('server_name', None)} with ID: {server_data.get('server_id', 0)}"
|
||||
return friendly_name
|
||||
|
||||
#************************************************************************************************
|
||||
@ -164,19 +185,82 @@ class helper_servers:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def sever_crashed(server_id):
|
||||
with database.atomic():
|
||||
Server_Stats.update(crashed=True).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def set_download(server_id):
|
||||
with database.atomic():
|
||||
Server_Stats.update(downloading=True).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def finish_download(server_id):
|
||||
with database.atomic():
|
||||
Server_Stats.update(downloading=False).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_download_status(server_id):
|
||||
download_status = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
|
||||
return download_status.downloading
|
||||
|
||||
|
||||
@staticmethod
|
||||
def server_crash_reset(server_id):
|
||||
with database.atomic():
|
||||
Server_Stats.update(crashed=False).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def is_crashed(server_id):
|
||||
svr = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
|
||||
#pylint: disable=singleton-comparison
|
||||
if svr.crashed == True:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def set_update(server_id, value):
|
||||
try:
|
||||
row = Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
#Checks if server even exists
|
||||
Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
except Exception as ex:
|
||||
logger.error("Database entry not found. ".format(ex))
|
||||
logger.error(f"Database entry not found! {ex}")
|
||||
with database.atomic():
|
||||
Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_update_status(server_id):
|
||||
update_status = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
|
||||
return update_status.updating
|
||||
|
||||
@staticmethod
|
||||
def set_first_run(server_id):
|
||||
#Sets first run to false
|
||||
try:
|
||||
#Checks if server even exists
|
||||
Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
except Exception as ex:
|
||||
logger.error(f"Database entry not found! {ex}")
|
||||
return
|
||||
with database.atomic():
|
||||
Server_Stats.update(first_run=False).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_first_run(server_id):
|
||||
first_run = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
|
||||
return first_run.first_run
|
||||
|
||||
@staticmethod
|
||||
def get_TTL_without_player(server_id):
|
||||
last_stat = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).first()
|
||||
last_stat_with_player = Server_Stats.select().where(Server_Stats.server_id == server_id).where(Server_Stats.online > 0).order_by(Server_Stats.created.desc()).first()
|
||||
last_stat_with_player = (Server_Stats
|
||||
.select()
|
||||
.where(Server_Stats.server_id == server_id)
|
||||
.where(Server_Stats.online > 0)
|
||||
.order_by(Server_Stats.created.desc())
|
||||
.first())
|
||||
return last_stat.created - last_stat_with_player.created
|
||||
|
||||
@staticmethod
|
||||
@ -186,13 +270,14 @@ class helper_servers:
|
||||
if (time_limit == -1) or (ttl_no_players > time_limit):
|
||||
can = True
|
||||
return can
|
||||
|
||||
|
||||
@staticmethod
|
||||
def set_waiting_start(server_id, value):
|
||||
try:
|
||||
row = Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
# Checks if server even exists
|
||||
Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
except Exception as ex:
|
||||
logger.error("Database entry not found. ".format(ex))
|
||||
logger.error(f"Database entry not found! {ex}")
|
||||
with database.atomic():
|
||||
Server_Stats.update(waiting_start=value).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@ -202,4 +287,4 @@ class helper_servers:
|
||||
return waiting_start.waiting_start
|
||||
|
||||
|
||||
servers_helper = helper_servers()
|
||||
servers_helper = helper_servers()
|
||||
|
@ -1,29 +1,21 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from typing import Optional, Union
|
||||
|
||||
from app.classes.models.roles import Roles, roles_helper
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
try:
|
||||
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, AutoField, DateTimeField, BooleanField, CompositeKey, DoesNotExist, JOIN
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
database = SqliteDatabase(helper.db_path, pragmas = {
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
@ -38,15 +30,36 @@ class Users(Model):
|
||||
last_ip = CharField(default="")
|
||||
username = CharField(default="", unique=True, index=True)
|
||||
password = CharField(default="")
|
||||
email = CharField(default="default@example.com")
|
||||
enabled = BooleanField(default=True)
|
||||
superuser = BooleanField(default=False)
|
||||
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
|
||||
lang = CharField(default="en_EN")
|
||||
support_logs = CharField(default = '')
|
||||
valid_tokens_from = DateTimeField(default=datetime.datetime.now)
|
||||
server_order = CharField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
database = database
|
||||
|
||||
|
||||
# ************************************************************************************************
|
||||
# API Keys Class
|
||||
# ************************************************************************************************
|
||||
class ApiKeys(Model):
|
||||
token_id = AutoField()
|
||||
name = CharField(default='', unique=True, index=True)
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
user_id = ForeignKeyField(Users, backref='api_token', index=True)
|
||||
server_permissions = CharField(default='00000000')
|
||||
crafty_permissions = CharField(default='000')
|
||||
superuser = BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = 'api_keys'
|
||||
database = database
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# User Roles Class
|
||||
#************************************************************************************************
|
||||
@ -70,7 +83,7 @@ class helper_users:
|
||||
|
||||
@staticmethod
|
||||
def get_all_users():
|
||||
query = Users.select()
|
||||
query = Users.select().where(Users.username != "system")
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
@ -79,25 +92,11 @@ class helper_users:
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_by_name(username):
|
||||
if username == "SYSTEM":
|
||||
return 0
|
||||
try:
|
||||
return (Users.get(Users.username == username)).user_id
|
||||
except DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_api_token(token: str):
|
||||
query = Users.select().where(Users.api_token == token)
|
||||
|
||||
if query.exists():
|
||||
user = model_to_dict(Users.get(Users.api_token == token))
|
||||
# I know it should apply it without setting it but I'm just making sure
|
||||
user = users_helper.add_user_roles(user)
|
||||
return user
|
||||
else:
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def user_query(user_id):
|
||||
user_query = Users.select().where(Users.user_id == user_id)
|
||||
@ -108,17 +107,18 @@ class helper_users:
|
||||
if user_id == 0:
|
||||
return {
|
||||
'user_id': 0,
|
||||
'created': None,
|
||||
'last_login': None,
|
||||
'last_update': None,
|
||||
'created': '10/24/2019, 11:34:00',
|
||||
'last_login': '10/24/2019, 11:34:00',
|
||||
'last_update': '10/24/2019, 11:34:00',
|
||||
'last_ip': "127.27.23.89",
|
||||
'username': "SYSTEM",
|
||||
'password': None,
|
||||
'email': "default@example.com",
|
||||
'enabled': True,
|
||||
'superuser': False,
|
||||
'api_token': None,
|
||||
'superuser': True,
|
||||
'roles': [],
|
||||
'servers': [],
|
||||
'support_logs': '',
|
||||
}
|
||||
user = model_to_dict(Users.get(Users.user_id == user_id))
|
||||
|
||||
@ -131,20 +131,30 @@ class helper_users:
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
|
||||
def check_system_user(user_id):
|
||||
try:
|
||||
result = Users.get(Users.user_id == user_id).user_id == user_id
|
||||
if result:
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_user_model(user_id: str) -> Users:
|
||||
user = Users.get(Users.user_id == user_id)
|
||||
user = users_helper.add_user_roles(user)
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def add_user(username: str, password: Optional[str] = None, email: Optional[str] = None, enabled: bool = True, superuser: bool = False) -> str:
|
||||
if password is not None:
|
||||
pw_enc = helper.encode_pass(password)
|
||||
else:
|
||||
pw_enc = None
|
||||
if api_token is None:
|
||||
api_token = users_helper.new_api_token()
|
||||
else:
|
||||
if type(api_token) is not str and len(api_token) != 32:
|
||||
raise ValueError("API token must be a 32 character string")
|
||||
user_id = Users.insert({
|
||||
Users.username: username.lower(),
|
||||
Users.password: pw_enc,
|
||||
Users.api_token: api_token,
|
||||
Users.email: email,
|
||||
Users.enabled: enabled,
|
||||
Users.superuser: superuser,
|
||||
Users.created: helper.get_time_as_string()
|
||||
@ -152,10 +162,30 @@ class helper_users:
|
||||
return user_id
|
||||
|
||||
@staticmethod
|
||||
def update_user(user_id, up_data={}):
|
||||
def update_user(user_id, up_data=None):
|
||||
if up_data is None:
|
||||
up_data = {}
|
||||
if up_data:
|
||||
Users.update(up_data).where(Users.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def update_server_order(user_id, user_server_order):
|
||||
Users.update(server_order = user_server_order).where(Users.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_server_order(user_id):
|
||||
return Users.select().where(Users.user_id == user_id)
|
||||
|
||||
@staticmethod
|
||||
def get_super_user_list():
|
||||
final_users = []
|
||||
# pylint: disable=singleton-comparison
|
||||
super_users = Users.select().where(Users.superuser == True)
|
||||
for suser in super_users:
|
||||
if suser.user_id not in final_users:
|
||||
final_users.append(suser.user_id)
|
||||
return final_users
|
||||
|
||||
@staticmethod
|
||||
def remove_user(user_id):
|
||||
with database.atomic():
|
||||
@ -163,20 +193,16 @@ class helper_users:
|
||||
user = Users.get(Users.user_id == user_id)
|
||||
return user.delete_instance()
|
||||
|
||||
@staticmethod
|
||||
def set_support_path(user_id, support_path):
|
||||
Users.update(support_logs = support_path).where(Users.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def user_id_exists(user_id):
|
||||
if not users_helper.get_user(user_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def new_api_token():
|
||||
while True:
|
||||
token = helper.random_string_generator(32)
|
||||
test = list(Users.select(Users.user_id).where(Users.api_token == token))
|
||||
if len(test) == 0:
|
||||
return token
|
||||
|
||||
#************************************************************************************************
|
||||
# User_Roles Methods
|
||||
#************************************************************************************************
|
||||
@ -209,8 +235,8 @@ class helper_users:
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def add_user_roles(user):
|
||||
if type(user) == dict:
|
||||
def add_user_roles(user: Union[dict, Users]):
|
||||
if isinstance(user, dict):
|
||||
user_id = user['user_id']
|
||||
else:
|
||||
user_id = user.user_id
|
||||
@ -223,7 +249,11 @@ class helper_users:
|
||||
for r in roles_query:
|
||||
roles.add(r.role_id.role_id)
|
||||
|
||||
user['roles'] = roles
|
||||
if isinstance(user, dict):
|
||||
user['roles'] = roles
|
||||
else:
|
||||
user.roles = roles
|
||||
|
||||
#logger.debug("user: ({}) {}".format(user_id, user))
|
||||
return user
|
||||
|
||||
@ -243,5 +273,41 @@ class helper_users:
|
||||
def remove_roles_from_role_id(role_id):
|
||||
User_Roles.delete().where(User_Roles.role_id == role_id).execute()
|
||||
|
||||
# ************************************************************************************************
|
||||
# ApiKeys Methods
|
||||
# ************************************************************************************************
|
||||
|
||||
users_helper = helper_users()
|
||||
@staticmethod
|
||||
def get_user_api_keys(user_id: str):
|
||||
return ApiKeys.select().where(ApiKeys.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_user_api_key(key_id: str) -> ApiKeys:
|
||||
return ApiKeys.get(ApiKeys.token_id == key_id)
|
||||
|
||||
@staticmethod
|
||||
def add_user_api_key(
|
||||
name: str,
|
||||
user_id: str,
|
||||
superuser: bool = False,
|
||||
server_permissions_mask: Optional[str] = None,
|
||||
crafty_permissions_mask: Optional[str] = None):
|
||||
return ApiKeys.insert({
|
||||
ApiKeys.name: name,
|
||||
ApiKeys.user_id: user_id,
|
||||
**({ApiKeys.server_permissions: server_permissions_mask} if server_permissions_mask is not None else {}),
|
||||
**({ApiKeys.crafty_permissions: crafty_permissions_mask} if crafty_permissions_mask is not None else {}),
|
||||
ApiKeys.superuser: superuser
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def delete_user_api_keys(user_id: str):
|
||||
ApiKeys.delete().where(ApiKeys.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def delete_user_api_key(key_id: str):
|
||||
ApiKeys.delete().where(ApiKeys.token_id == key_id).execute()
|
||||
|
||||
|
||||
|
||||
users_helper = helper_users()
|
||||
|
79
app/classes/shared/authentication.py
Normal file
79
app/classes/shared/authentication.py
Normal file
@ -0,0 +1,79 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, Dict, Any, Tuple
|
||||
|
||||
from app.classes.models.users import users_helper, ApiKeys
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
try:
|
||||
import jwt
|
||||
from jwt import PyJWTError
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Authentication:
|
||||
def __init__(self):
|
||||
self.secret = "my secret"
|
||||
self.secret = helper.get_setting('apikey_secret', None)
|
||||
|
||||
if self.secret is None or self.secret == 'random':
|
||||
self.secret = helper.random_string_generator(64)
|
||||
|
||||
@staticmethod
|
||||
def generate(user_id, extra=None):
|
||||
if extra is None:
|
||||
extra = {}
|
||||
return jwt.encode(
|
||||
{
|
||||
'user_id': user_id,
|
||||
'iat': int(time.time()),
|
||||
**extra
|
||||
},
|
||||
authentication.secret,
|
||||
algorithm="HS256"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def read(token):
|
||||
return jwt.decode(token, authentication.secret, algorithms=["HS256"])
|
||||
|
||||
@staticmethod
|
||||
def check_no_iat(token) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
return jwt.decode(token, authentication.secret, algorithms=["HS256"])
|
||||
except PyJWTError as error:
|
||||
logger.debug("Error while checking JWT token: ", exc_info=error)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def check(token) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
|
||||
try:
|
||||
data = jwt.decode(token, authentication.secret, algorithms=["HS256"])
|
||||
except PyJWTError as error:
|
||||
logger.debug("Error while checking JWT token: ", exc_info=error)
|
||||
return None
|
||||
iat: int = data['iat']
|
||||
key: Optional[ApiKeys] = None
|
||||
if 'token_id' in data:
|
||||
key_id = data['token_id']
|
||||
key = users_helper.get_user_api_key(key_id)
|
||||
if key is None:
|
||||
return None
|
||||
user_id: str = data['user_id']
|
||||
user = users_helper.get_user(user_id)
|
||||
# TODO: Have a cache or something so we don't constantly have to query the database
|
||||
if int(user.get('valid_tokens_from').timestamp()) < iat:
|
||||
# Success!
|
||||
return key, data, user
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def check_bool(token) -> bool:
|
||||
return authentication.check(token) is not None
|
||||
|
||||
|
||||
authentication = Authentication()
|
@ -1,26 +1,16 @@
|
||||
import os
|
||||
import sys
|
||||
import cmd
|
||||
import time
|
||||
import threading
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
try:
|
||||
import requests
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class MainPrompt(cmd.Cmd, object):
|
||||
class MainPrompt(cmd.Cmd):
|
||||
|
||||
def __init__(self, tasks_manager, migration_manager):
|
||||
super().__init__()
|
||||
@ -28,67 +18,60 @@ class MainPrompt(cmd.Cmd, object):
|
||||
self.migration_manager = migration_manager
|
||||
|
||||
# overrides the default Prompt
|
||||
prompt = "Crafty Controller v{} > ".format(helper.get_version_string())
|
||||
prompt = f"Crafty Controller v{helper.get_version_string()} > "
|
||||
|
||||
@staticmethod
|
||||
def emptyline():
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _clean_shutdown():
|
||||
exit_file = os.path.join(helper.root_dir, "exit.txt")
|
||||
try:
|
||||
with open(exit_file, 'w') as f:
|
||||
f.write("exit")
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Unable to write exit file due to error: {}".format(e))
|
||||
console.critical("Unable to write exit file due to error: {}".format(e))
|
||||
|
||||
#pylint: disable=unused-argument
|
||||
def do_exit(self, line):
|
||||
self.tasks_manager._main_graceful_exit()
|
||||
self.universal_exit()
|
||||
|
||||
|
||||
def do_migrations(self, line):
|
||||
if (line == 'up'):
|
||||
if line == 'up':
|
||||
self.migration_manager.up()
|
||||
elif (line == 'down'):
|
||||
elif line == 'down':
|
||||
self.migration_manager.down()
|
||||
elif (line == 'done'):
|
||||
elif line == 'done':
|
||||
console.info(self.migration_manager.done)
|
||||
elif (line == 'todo'):
|
||||
elif line == 'todo':
|
||||
console.info(self.migration_manager.todo)
|
||||
elif (line == 'diff'):
|
||||
elif line == 'diff':
|
||||
console.info(self.migration_manager.diff)
|
||||
elif (line == 'info'):
|
||||
console.info('Done: {}'.format(self.migration_manager.done))
|
||||
console.info('FS: {}'.format(self.migration_manager.todo))
|
||||
console.info('Todo: {}'.format(self.migration_manager.diff))
|
||||
elif (line.startswith('add ')):
|
||||
elif line == 'info':
|
||||
console.info(f'Done: {self.migration_manager.done}')
|
||||
console.info(f'FS: {self.migration_manager.todo}')
|
||||
console.info(f'Todo: {self.migration_manager.diff}')
|
||||
elif line.startswith('add '):
|
||||
migration_name = line[len('add '):]
|
||||
self.migration_manager.create(migration_name, False)
|
||||
else:
|
||||
console.info('Unknown migration command')
|
||||
|
||||
def do_threads(self, line):
|
||||
|
||||
@staticmethod
|
||||
def do_threads(_line):
|
||||
for thread in threading.enumerate():
|
||||
print(f'Name: {thread.name} IDENT: {thread.ident}')
|
||||
if sys.version_info >= (3, 8):
|
||||
print(f'Name: {thread.name} Identifier: {thread.ident} TID/PID: {thread.native_id}')
|
||||
else:
|
||||
print(f'Name: {thread.name} Identifier: {thread.ident}')
|
||||
|
||||
def universal_exit(self):
|
||||
logger.info("Stopping all server daemons / threads")
|
||||
console.info("Stopping all server daemons / threads - This may take a few seconds")
|
||||
websocket_helper.disconnect_all()
|
||||
self._clean_shutdown()
|
||||
console.info('Waiting for main thread to stop')
|
||||
while True:
|
||||
if self.tasks_manager.get_main_thread_run_status():
|
||||
sys.exit(0)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def help_exit():
|
||||
console.help("Stops the server if running, Exits the program")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def help_migrations():
|
||||
console.help("Only for advanced users. Use with caution")
|
@ -8,14 +8,11 @@ try:
|
||||
from colorama import init
|
||||
from termcolor import colored
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
print("Import Error: Unable to load {} module".format(e.name))
|
||||
except ModuleNotFoundError as ex:
|
||||
logger.critical(f"Import Error: Unable to load {ex.name} module", exc_info=True)
|
||||
print(f"Import Error: Unable to load {ex.name} module")
|
||||
from app.classes.shared.installer import installer
|
||||
installer.do_install()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class Console:
|
||||
|
||||
def __init__(self):
|
||||
@ -49,28 +46,27 @@ class Console:
|
||||
|
||||
def debug(self, message):
|
||||
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
self.magenta("[+] Crafty: {} - DEBUG:\t{}".format(dt, message))
|
||||
self.magenta(f"[+] Crafty: {dt} - DEBUG:\t{message}")
|
||||
|
||||
def info(self, message):
|
||||
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
self.white("[+] Crafty: {} - INFO:\t{}".format(dt, message))
|
||||
self.white(f"[+] Crafty: {dt} - INFO:\t{message}")
|
||||
|
||||
def warning(self, message):
|
||||
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
self.cyan("[+] Crafty: {} - WARNING:\t{}".format(dt, message))
|
||||
self.cyan(f"[+] Crafty: {dt} - WARNING:\t{message}")
|
||||
|
||||
def error(self, message):
|
||||
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
self.yellow("[+] Crafty: {} - ERROR:\t{}".format(dt, message))
|
||||
self.yellow(f"[+] Crafty: {dt} - ERROR:\t{message}")
|
||||
|
||||
def critical(self, message):
|
||||
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
self.red("[+] Crafty: {} - CRITICAL:\t{}".format(dt, message))
|
||||
self.red(f"[+] Crafty: {dt} - CRITICAL:\t{message}")
|
||||
|
||||
def help(self, message):
|
||||
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
self.green("[+] Crafty: {} - HELP:\t{}".format(dt, message))
|
||||
self.green(f"[+] Crafty: {dt} - HELP:\t{message}")
|
||||
|
||||
|
||||
console = Console()
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
class CraftyException(Exception):
|
||||
pass
|
||||
pass
|
||||
|
||||
class DatabaseException(CraftyException):
|
||||
pass
|
||||
pass
|
||||
|
||||
class SchemaError(DatabaseException):
|
||||
pass
|
||||
pass
|
||||
|
101
app/classes/shared/file_helpers.py
Normal file
101
app/classes/shared/file_helpers.py
Normal file
@ -0,0 +1,101 @@
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
import pathlib
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FileHelpers:
|
||||
allowed_quotes = [
|
||||
"\"",
|
||||
"'",
|
||||
"`"
|
||||
]
|
||||
|
||||
def del_dirs(self, path):
|
||||
path = pathlib.Path(path)
|
||||
for sub in path.iterdir():
|
||||
if sub.is_dir():
|
||||
# Delete folder if it is a folder
|
||||
self.del_dirs(sub)
|
||||
else:
|
||||
# Delete file if it is a file:
|
||||
sub.unlink()
|
||||
|
||||
# This removes the top-level folder:
|
||||
path.rmdir()
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def del_file(path):
|
||||
path = pathlib.Path(path)
|
||||
try:
|
||||
logger.debug(f"Deleting file: {path}")
|
||||
#Remove the file
|
||||
os.remove(path)
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Path specified is not a file or does not exist. {path}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def copy_dir(src_path, dest_path, dirs_exist_ok=False):
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
shutil.copytree(src_path, dest_path, dirs_exist_ok=dirs_exist_ok)
|
||||
|
||||
@staticmethod
|
||||
def copy_file(src_path, dest_path):
|
||||
shutil.copy(src_path, dest_path)
|
||||
|
||||
def move_dir(self, src_path, dest_path):
|
||||
self.copy_dir(src_path, dest_path)
|
||||
self.del_dirs(src_path)
|
||||
|
||||
def move_file(self, src_path, dest_path):
|
||||
self.copy_file(src_path, dest_path)
|
||||
self.del_file(src_path)
|
||||
|
||||
@staticmethod
|
||||
def make_archive(path_to_destination, path_to_zip):
|
||||
# create a ZipFile object
|
||||
path_to_destination += '.zip'
|
||||
with ZipFile(path_to_destination, 'w') as z:
|
||||
for root, _dirs, files in os.walk(path_to_zip, topdown=True):
|
||||
ziproot = path_to_zip
|
||||
for file in files:
|
||||
try:
|
||||
logger.info(f"backing up: {os.path.join(root, file)}")
|
||||
if os.name == "nt":
|
||||
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, ""), file))
|
||||
else:
|
||||
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, "/"), file))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error backing up: {os.path.join(root, file)}! - Error was: {e}")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def make_compressed_archive(path_to_destination, path_to_zip):
|
||||
# create a ZipFile object
|
||||
path_to_destination += '.zip'
|
||||
with ZipFile(path_to_destination, 'w', ZIP_DEFLATED) as z:
|
||||
for root, _dirs, files in os.walk(path_to_zip, topdown=True):
|
||||
ziproot = path_to_zip
|
||||
for file in files:
|
||||
try:
|
||||
logger.info(f"backing up: {os.path.join(root, file)}")
|
||||
if os.name == "nt":
|
||||
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, ""), file))
|
||||
else:
|
||||
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, "/"), file))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error backing up: {os.path.join(root, file)}! - Error was: {e}")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
file_helper = FileHelpers()
|
@ -13,25 +13,29 @@ import logging
|
||||
import html
|
||||
import zipfile
|
||||
import pathlib
|
||||
import shutil
|
||||
from requests import get
|
||||
|
||||
import ctypes
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
from contextlib import suppress
|
||||
import psutil
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.installer import installer
|
||||
from app.classes.shared.file_helpers import file_helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import requests
|
||||
from requests import get
|
||||
from OpenSSL import crypto
|
||||
from argon2 import PasswordHasher
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
except ModuleNotFoundError as err:
|
||||
logger.critical(f"Import Error: Unable to load {err.name} module", exc_info=True)
|
||||
print(f"Import Error: Unable to load {err.name} module")
|
||||
installer.do_install()
|
||||
|
||||
class Helpers:
|
||||
allowed_quotes = [
|
||||
@ -58,14 +62,20 @@ class Helpers:
|
||||
self.passhasher = PasswordHasher()
|
||||
self.exiting = False
|
||||
|
||||
@staticmethod
|
||||
def auto_installer_fix(ex):
|
||||
logger.critical(f"Import Error: Unable to load {ex.name} module", exc_info=True)
|
||||
print(f"Import Error: Unable to load {ex.name} module")
|
||||
installer.do_install()
|
||||
|
||||
def float_to_string(self, gbs: int):
|
||||
s = str(float(gbs) * 1000).rstrip("0").rstrip(".")
|
||||
return s
|
||||
|
||||
def check_file_perms(self, path):
|
||||
try:
|
||||
fp = open(path, "r").close()
|
||||
logger.info("{} is readable".format(path))
|
||||
open(path, "r", encoding='utf-8').close()
|
||||
logger.info(f"{path} is readable")
|
||||
return True
|
||||
except PermissionError:
|
||||
return False
|
||||
@ -78,30 +88,52 @@ class Helpers:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
logger.error("{} does not exist".format(file))
|
||||
logger.error(f"{file} does not exist")
|
||||
return True
|
||||
|
||||
def get_servers_root_dir(self):
|
||||
return self.servers_dir
|
||||
|
||||
@staticmethod
|
||||
def check_internet():
|
||||
try:
|
||||
requests.get('https://google.com', timeout=1)
|
||||
return True
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_port(server_port):
|
||||
try:
|
||||
host_public = get('https://api.ipify.org').text
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10.0)
|
||||
result = sock.connect_ex((host_public ,server_port))
|
||||
sock.close()
|
||||
if result == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as err:
|
||||
ip = get('https://api.ipify.org').content.decode('utf8')
|
||||
except:
|
||||
ip = 'google.com'
|
||||
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
a_socket.settimeout(20.0)
|
||||
|
||||
location = (ip, server_port)
|
||||
result_of_check = a_socket.connect_ex(location)
|
||||
|
||||
a_socket.close()
|
||||
|
||||
if result_of_check == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_server_conn(server_port):
|
||||
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
a_socket.settimeout(10.0)
|
||||
ip = '127.0.0.1'
|
||||
|
||||
location = (ip, server_port)
|
||||
result_of_check = a_socket.connect_ex(location)
|
||||
a_socket.close()
|
||||
|
||||
if result_of_check == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@ -113,7 +145,7 @@ class Helpers:
|
||||
esc = False # whether an escape character was encountered
|
||||
stch = None # if we're dealing with a quote, save the quote type here. Nested quotes to be dealt with by the command
|
||||
for c in cmd_in: # for character in string
|
||||
if np == True: # if set, begin a new argument and increment the command index. Continue the loop.
|
||||
if np: # if set, begin a new argument and increment the command index. Continue the loop.
|
||||
if c == ' ':
|
||||
continue
|
||||
else:
|
||||
@ -128,52 +160,35 @@ class Helpers:
|
||||
else:
|
||||
if c == '\\': # if the current character is an escape character, set the esc flag and continue to next loop
|
||||
esc = True
|
||||
elif c == ' ' and stch is None: # if we encounter a space and are not dealing with a quote, set the new argument flag and continue to next loop
|
||||
elif c == ' ' and stch is None: # if we encounter a space and are not dealing with a quote,
|
||||
# set the new argument flag and continue to next loop
|
||||
np = True
|
||||
elif c == stch: # if we encounter the character that matches our start quote, end the quote and continue to next loop
|
||||
stch = None
|
||||
elif stch is None and (c in Helpers.allowed_quotes): # if we're not in the middle of a quote and we get a quotable character, start a quote and proceed to the next loop
|
||||
elif stch is None and (c in Helpers.allowed_quotes): # if we're not in the middle of a quote and we get a quotable character,
|
||||
# start a quote and proceed to the next loop
|
||||
stch = c
|
||||
else: # else, just store the character in the current arg
|
||||
cmd_out[ci] += c
|
||||
return cmd_out
|
||||
|
||||
def check_for_old_logs(self, db_helper):
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
for server in servers:
|
||||
logs_path = os.path.split(server['log_path'])[0]
|
||||
latest_log_file = os.path.split(server['log_path'])[1]
|
||||
logs_delete_after = int(server['logs_delete_after'])
|
||||
if logs_delete_after == 0:
|
||||
continue
|
||||
|
||||
log_files = list(filter(
|
||||
lambda val: val != latest_log_file,
|
||||
os.listdir(logs_path)
|
||||
))
|
||||
for log_file in log_files:
|
||||
log_file_path = os.path.join(logs_path, log_file)
|
||||
if self.check_file_exists(log_file_path) and \
|
||||
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
|
||||
os.remove(log_file_path)
|
||||
|
||||
def get_setting(self, key, default_return=False):
|
||||
|
||||
try:
|
||||
with open(self.settings_file, "r") as f:
|
||||
with open(self.settings_file, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if key in data.keys():
|
||||
return data.get(key)
|
||||
|
||||
else:
|
||||
logger.error("Config File Error: setting {} does not exist".format(key))
|
||||
console.error("Config File Error: setting {} does not exist".format(key))
|
||||
logger.error(f"Config File Error: setting {key} does not exist")
|
||||
console.error(f"Config File Error: setting {key} does not exist")
|
||||
return default_return
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Config File Error: Unable to read {} due to {}".format(self.settings_file, e))
|
||||
console.critical("Config File Error: Unable to read {} due to {}".format(self.settings_file, e))
|
||||
logger.critical(f"Config File Error: Unable to read {self.settings_file} due to {e}")
|
||||
console.critical(f"Config File Error: Unable to read {self.settings_file} due to {e}")
|
||||
|
||||
return default_return
|
||||
|
||||
@ -192,11 +207,11 @@ class Helpers:
|
||||
def get_version(self):
|
||||
version_data = {}
|
||||
try:
|
||||
with open(os.path.join(self.config_dir, 'version.json'), 'r') as f:
|
||||
with open(os.path.join(self.config_dir, 'version.json'), 'r', encoding='utf-8') as f:
|
||||
version_data = json.load(f)
|
||||
|
||||
except Exception as e:
|
||||
console.critical("Unable to get version data!")
|
||||
console.critical(f"Unable to get version data! \n{e}")
|
||||
|
||||
return version_data
|
||||
|
||||
@ -210,7 +225,7 @@ class Helpers:
|
||||
try:
|
||||
data = json.loads(r.content)
|
||||
except Exception as e:
|
||||
logger.error("Failed to load json content with error: {} ".format(e))
|
||||
logger.error(f"Failed to load json content with error: {e}")
|
||||
|
||||
return data
|
||||
|
||||
@ -218,23 +233,15 @@ class Helpers:
|
||||
def get_version_string(self):
|
||||
|
||||
version_data = self.get_version()
|
||||
major = version_data.get('major', '?')
|
||||
minor = version_data.get('minor', '?')
|
||||
sub = version_data.get('sub', '?')
|
||||
meta = version_data.get('meta', '?')
|
||||
|
||||
# set some defaults if we don't get version_data from our helper
|
||||
version = "{}.{}.{}-{}".format(version_data.get('major', '?'),
|
||||
version_data.get('minor', '?'),
|
||||
version_data.get('sub', '?'),
|
||||
version_data.get('meta', '?'))
|
||||
version = f"{major}.{minor}.{sub}-{meta}"
|
||||
return str(version)
|
||||
|
||||
def do_exit(self):
|
||||
exit_file = os.path.join(self.root_dir, 'exit.txt')
|
||||
try:
|
||||
open(exit_file, 'a').close()
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Unable to create exit file!")
|
||||
console.critical("Unable to create exit file!")
|
||||
sys.exit(1)
|
||||
|
||||
def encode_pass(self, password):
|
||||
return self.passhasher.hash(password)
|
||||
|
||||
@ -243,7 +250,6 @@ class Helpers:
|
||||
self.passhasher.verify(currenthash, password)
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
def log_colors(self, line):
|
||||
@ -256,15 +262,18 @@ class Helpers:
|
||||
(r'(\[.+?/INFO\])', r'<span class="mc-log-info">\1</span>'),
|
||||
(r'(\[.+?/WARN\])', r'<span class="mc-log-warn">\1</span>'),
|
||||
(r'(\[.+?/ERROR\])', r'<span class="mc-log-error">\1</span>'),
|
||||
(r'(\[.+?/FATAL\])', r'<span class="mc-log-fatal">\1</span>'),
|
||||
(r'(\w+?\[/\d+?\.\d+?\.\d+?\.\d+?\:\d+?\])', r'<span class="mc-log-keyword">\1</span>'),
|
||||
(r'\[(\d\d:\d\d:\d\d)\]', r'<span class="mc-log-time">[\1]</span>'),
|
||||
(r'(\[.+? INFO\])', r'<span class="mc-log-info">\1</span>'),
|
||||
(r'(\[.+? WARN\])', r'<span class="mc-log-warn">\1</span>'),
|
||||
(r'(\[.+? ERROR\])', r'<span class="mc-log-error">\1</span>')
|
||||
(r'(\[.+? ERROR\])', r'<span class="mc-log-error">\1</span>'),
|
||||
(r'(\[.+? FATAL\])', r'<span class="mc-log-fatal">\1</span>')
|
||||
]
|
||||
|
||||
# highlight users keywords
|
||||
for keyword in user_keywords:
|
||||
# pylint: disable=consider-using-f-string
|
||||
search_replace = (r'({})'.format(keyword), r'<span class="mc-log-keyword">\1</span>')
|
||||
replacements.append(search_replace)
|
||||
|
||||
@ -273,10 +282,23 @@ class Helpers:
|
||||
|
||||
return line
|
||||
|
||||
|
||||
def validate_traversal(self, base_path, filename):
|
||||
logger.debug(f"Validating traversal (\"{base_path}\", \"{filename}\")")
|
||||
base = pathlib.Path(base_path).resolve()
|
||||
file = pathlib.Path(filename)
|
||||
fileabs = base.joinpath(file).resolve()
|
||||
cp = pathlib.Path(os.path.commonpath([base, fileabs]))
|
||||
if base == cp:
|
||||
return fileabs
|
||||
else:
|
||||
raise ValueError("Path traversal detected")
|
||||
|
||||
|
||||
def tail_file(self, file_name, number_lines=20):
|
||||
if not self.check_file_exists(file_name):
|
||||
logger.warning("Unable to find file to tail: {}".format(file_name))
|
||||
return ["Unable to find file to tail: {}".format(file_name)]
|
||||
logger.warning(f"Unable to find file to tail: {file_name}")
|
||||
return [f"Unable to find file to tail: {file_name}"]
|
||||
|
||||
# length of lines is X char here
|
||||
avg_line_length = 255
|
||||
@ -285,7 +307,7 @@ class Helpers:
|
||||
line_buffer = number_lines * avg_line_length
|
||||
|
||||
# open our file
|
||||
with open(file_name, 'r') as f:
|
||||
with open(file_name, 'r', encoding='utf-8') as f:
|
||||
|
||||
# seek
|
||||
f.seek(0, 2)
|
||||
@ -301,8 +323,7 @@ class Helpers:
|
||||
lines = f.readlines()
|
||||
|
||||
except Exception as e:
|
||||
logger.warning('Unable to read a line in the file:{} - due to error: {}'.format(file_name, e))
|
||||
pass
|
||||
logger.warning(f'Unable to read a line in the file:{file_name} - due to error: {e}')
|
||||
|
||||
# now we are done getting the lines, let's return it
|
||||
return lines
|
||||
@ -311,16 +332,28 @@ class Helpers:
|
||||
def check_writeable(path: str):
|
||||
filename = os.path.join(path, "tempfile.txt")
|
||||
try:
|
||||
fp = open(filename, "w").close()
|
||||
open(filename, "w", encoding='utf-8').close()
|
||||
os.remove(filename)
|
||||
|
||||
logger.info("{} is writable".format(filename))
|
||||
logger.info(f"{filename} is writable")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Unable to write to {} - Error: {}".format(path, e))
|
||||
logger.critical(f"Unable to write to {path} - Error: {e}")
|
||||
return False
|
||||
|
||||
def checkRoot(self):
|
||||
if self.is_os_windows():
|
||||
if ctypes.windll.shell32.IsUserAnAdmin() == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
if os.geteuid() == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def unzipFile(self, zip_path):
|
||||
new_dir_list = zip_path.split('/')
|
||||
new_dir = ''
|
||||
@ -336,24 +369,17 @@ class Helpers:
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(tempDir)
|
||||
for i in range(len(zip_ref.filelist)):
|
||||
for i in enumerate(zip_ref.filelist):
|
||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
|
||||
test = zip_ref.filelist[i].filename
|
||||
break
|
||||
path_list = test.split('/')
|
||||
root_path = path_list[0]
|
||||
'''
|
||||
if len(path_list) > 1:
|
||||
for i in range(len(path_list) - 2):
|
||||
root_path = os.path.join(root_path, path_list[i + 1])
|
||||
'''
|
||||
|
||||
full_root_path = tempDir
|
||||
|
||||
for item in os.listdir(full_root_path):
|
||||
try:
|
||||
shutil.move(os.path.join(full_root_path, item), os.path.join(new_dir, item))
|
||||
file_helper.move_dir(os.path.join(full_root_path, item), os.path.join(new_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
|
||||
logger.error(f'ERROR IN ZIP IMPORT: {ex}')
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
else:
|
||||
@ -370,27 +396,28 @@ class Helpers:
|
||||
|
||||
# if not writeable, let's bomb out
|
||||
if not writeable:
|
||||
logger.critical("Unable to write to {} directory!".format(self.root_dir))
|
||||
logger.critical(f"Unable to write to {self.root_dir} directory!")
|
||||
sys.exit(1)
|
||||
|
||||
# ensure the log directory is there
|
||||
try:
|
||||
os.makedirs(os.path.join(self.root_dir, 'logs'))
|
||||
with suppress(FileExistsError):
|
||||
os.makedirs(os.path.join(self.root_dir, 'logs'))
|
||||
except Exception as e:
|
||||
console.error("Failed to make logs directory with error: {} ".format(e))
|
||||
console.error(f"Failed to make logs directory with error: {e} ")
|
||||
|
||||
# ensure the log file is there
|
||||
try:
|
||||
open(log_file, 'a').close()
|
||||
open(log_file, 'a', encoding='utf-8').close()
|
||||
except Exception as e:
|
||||
console.critical("Unable to open log file!")
|
||||
console.critical(f"Unable to open log file! {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# del any old session.lock file as this is a new session
|
||||
try:
|
||||
os.remove(session_log_file)
|
||||
except Exception as e:
|
||||
logger.error("Deleting Session.lock failed with error: {} ".format(e))
|
||||
logger.error(f"Deleting Session.lock failed with error: {e}")
|
||||
|
||||
@staticmethod
|
||||
def get_time_as_string():
|
||||
@ -399,10 +426,10 @@ class Helpers:
|
||||
|
||||
@staticmethod
|
||||
def check_file_exists(path: str):
|
||||
logger.debug('Looking for path: {}'.format(path))
|
||||
logger.debug(f'Looking for path: {path}')
|
||||
|
||||
if os.path.exists(path) and os.path.isfile(path):
|
||||
logger.debug('Found path: {}'.format(path))
|
||||
logger.debug(f'Found path: {path}')
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -411,18 +438,20 @@ class Helpers:
|
||||
def human_readable_file_size(num: int, suffix='B'):
|
||||
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
||||
if abs(num) < 1024.0:
|
||||
# pylint: disable=consider-using-f-string
|
||||
return "%3.1f%s%s" % (num, unit, suffix)
|
||||
num /= 1024.0
|
||||
# pylint: disable=consider-using-f-string
|
||||
return "%.1f%s%s" % (num, 'Y', suffix)
|
||||
|
||||
@staticmethod
|
||||
def check_path_exists(path: str):
|
||||
if not path:
|
||||
return False
|
||||
logger.debug('Looking for path: {}'.format(path))
|
||||
logger.debug(f'Looking for path: {path}')
|
||||
|
||||
if os.path.exists(path):
|
||||
logger.debug('Found path: {}'.format(path))
|
||||
logger.debug(f'Found path: {path}')
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -434,17 +463,17 @@ class Helpers:
|
||||
|
||||
if os.path.exists(path) and os.path.isfile(path):
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
for line in (f.readlines() [-lines:]):
|
||||
contents = contents + line
|
||||
|
||||
return contents
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to read file: {}. \n Error: ".format(path, e))
|
||||
logger.error(f"Unable to read file: {path}. \n Error: {e}")
|
||||
return False
|
||||
else:
|
||||
logger.error("Unable to read file: {}. File not found, or isn't a file.".format(path))
|
||||
logger.error(f"Unable to read file: {path}. File not found, or isn't a file.")
|
||||
return False
|
||||
|
||||
def create_session_file(self, ignore=False):
|
||||
@ -459,13 +488,19 @@ class Helpers:
|
||||
data = json.loads(file_data)
|
||||
pid = data.get('pid')
|
||||
started = data.get('started')
|
||||
console.critical("Another Crafty Controller agent seems to be running...\npid: {} \nstarted on: {}".format(pid, started))
|
||||
except Exception as e:
|
||||
logger.error("Failed to locate existing session.lock with error: {} ".format(e))
|
||||
console.error("Failed to locate existing session.lock with error: {} ".format(e))
|
||||
|
||||
if psutil.pid_exists(pid):
|
||||
console.critical(f"Another Crafty Controller agent seems to be running...\npid: {pid} \nstarted on: {started}")
|
||||
logger.critical("Found running crafty process. Exiting.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
logger.info("No process found for pid. Assuming crafty crashed. Deleting stale session.lock")
|
||||
os.remove(self.session_file)
|
||||
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to locate existing session.lock with error: {e} ")
|
||||
console.error(f"Failed to locate existing session.lock with error: {e} ")
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
pid = os.getpid()
|
||||
now = datetime.now()
|
||||
@ -474,7 +509,7 @@ class Helpers:
|
||||
'pid': pid,
|
||||
'started': now.strftime("%d-%m-%Y, %H:%M:%S")
|
||||
}
|
||||
with open(self.session_file, 'w') as f:
|
||||
with open(self.session_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(session_data, f, indent=True)
|
||||
|
||||
# because this is a recursive function, we will return bytes, and set human readable later
|
||||
@ -501,14 +536,14 @@ class Helpers:
|
||||
return sizes
|
||||
|
||||
@staticmethod
|
||||
def base64_encode_string(string: str):
|
||||
s_bytes = str(string).encode('utf-8')
|
||||
def base64_encode_string(fun_str: str):
|
||||
s_bytes = str(fun_str).encode('utf-8')
|
||||
b64_bytes = base64.encodebytes(s_bytes)
|
||||
return b64_bytes.decode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def base64_decode_string(string: str):
|
||||
s_bytes = str(string).encode('utf-8')
|
||||
def base64_decode_string(fun_str: str):
|
||||
s_bytes = str(fun_str).encode('utf-8')
|
||||
b64_bytes = base64.decodebytes(s_bytes)
|
||||
return b64_bytes.decode("utf-8")
|
||||
|
||||
@ -528,7 +563,7 @@ class Helpers:
|
||||
|
||||
try:
|
||||
os.makedirs(path)
|
||||
logger.debug("Created Directory : {}".format(path))
|
||||
logger.debug(f"Created Directory : {path}")
|
||||
|
||||
# directory already exists - non-blocking error
|
||||
except FileExistsError:
|
||||
@ -545,8 +580,8 @@ class Helpers:
|
||||
cert_file = os.path.join(cert_dir, 'commander.cert.pem')
|
||||
key_file = os.path.join(cert_dir, 'commander.key.pem')
|
||||
|
||||
logger.info("SSL Cert File is set to: {}".format(cert_file))
|
||||
logger.info("SSL Key File is set to: {}".format(key_file))
|
||||
logger.info(f"SSL Cert File is set to: {cert_file}")
|
||||
logger.info(f"SSL Key File is set to: {key_file}")
|
||||
|
||||
# don't create new files if we already have them.
|
||||
if self.check_file_exists(cert_file) and self.check_file_exists(key_file):
|
||||
@ -577,11 +612,11 @@ class Helpers:
|
||||
cert.set_pubkey(k)
|
||||
cert.sign(k, 'sha256')
|
||||
|
||||
f = open(cert_file, "w")
|
||||
f = open(cert_file, "w", encoding='utf-8')
|
||||
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode())
|
||||
f.close()
|
||||
|
||||
f = open(key_file, "w")
|
||||
f = open(key_file, "w", encoding='utf-8')
|
||||
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode())
|
||||
f.close()
|
||||
|
||||
@ -601,12 +636,26 @@ class Helpers:
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def wtol_path(w_path):
|
||||
l_path = w_path.replace('\\', '/')
|
||||
return l_path
|
||||
|
||||
@staticmethod
|
||||
def ltow_path(l_path):
|
||||
w_path = l_path.replace('/', '\\')
|
||||
return w_path
|
||||
|
||||
@staticmethod
|
||||
def get_os_understandable_path(path):
|
||||
return os.path.normpath(path)
|
||||
|
||||
def find_default_password(self):
|
||||
default_file = os.path.join(self.root_dir, "default.json")
|
||||
data = {}
|
||||
|
||||
if self.check_file_exists(default_file):
|
||||
with open(default_file, 'r') as f:
|
||||
with open(default_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
del_json = helper.get_setting('delete_default_json')
|
||||
@ -618,37 +667,169 @@ class Helpers:
|
||||
|
||||
@staticmethod
|
||||
def generate_tree(folder, output=""):
|
||||
for raw_filename in os.listdir(folder):
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold)
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{filename}
|
||||
</span>
|
||||
</div><li>
|
||||
\n"""\
|
||||
|
||||
else:
|
||||
if filename != "crafty_managed.txt":
|
||||
output += f"""<li
|
||||
class="tree-nested d-block tree-ctx-item tree-file tree-item"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{filename}</li>"""
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def generate_dir(folder, output=""):
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold)
|
||||
output += \
|
||||
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
|
||||
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
"""<li class="tree-item" data-path="{}">
|
||||
\n<div data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{}
|
||||
</div>
|
||||
\n<ul class="tree-nested">"""\
|
||||
.format(os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
|
||||
{filename}
|
||||
</span>
|
||||
</div><li>"""\
|
||||
|
||||
output += helper.generate_tree(rel)
|
||||
output += '</ul>\n</li>'
|
||||
else:
|
||||
output += """<li
|
||||
class="tree-item tree-ctx-item tree-file"
|
||||
data-path="{}"
|
||||
data-name="{}"
|
||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{}</li>""".format(os.path.join(folder, filename), filename, filename)
|
||||
if filename != "crafty_managed.txt":
|
||||
output += f"""<li
|
||||
class="tree-nested d-block tree-ctx-item tree-file tree-item"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{filename}</li>"""
|
||||
output += '</ul>\n'
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def generate_zip_tree(folder, output=""):
|
||||
file_list = os.listdir(folder)
|
||||
file_list = sorted(file_list, key=str.casefold)
|
||||
output += \
|
||||
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
|
||||
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\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 class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{filename}
|
||||
</span>
|
||||
</input></div><li>
|
||||
\n"""\
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def generate_zip_dir(folder, output=""):
|
||||
file_list = os.listdir(folder)
|
||||
file_list = sorted(file_list, key=str.casefold)
|
||||
output += \
|
||||
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
|
||||
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\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 class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{filename}
|
||||
</span>
|
||||
</input></div><li>"""\
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def unzipServer(zip_path, user_id):
|
||||
if helper.check_file_perms(zip_path):
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
#extracts archive to temp directory
|
||||
zip_ref.extractall(tempDir)
|
||||
if user_id:
|
||||
websocket_helper.broadcast_user(user_id, 'send_temp_path',{
|
||||
'path': tempDir
|
||||
})
|
||||
@staticmethod
|
||||
def backup_select(path, user_id):
|
||||
if user_id:
|
||||
websocket_helper.broadcast_user(user_id, 'send_temp_path',{
|
||||
'path': path
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def unzip_backup_archive(backup_path, zip_name):
|
||||
zip_path = os.path.join(backup_path, zip_name)
|
||||
if helper.check_file_perms(zip_path):
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
#extracts archive to temp directory
|
||||
zip_ref.extractall(tempDir)
|
||||
return tempDir
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def in_path(parent_path, child_path):
|
||||
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
|
||||
parent_path = os.path.abspath(parent_path)
|
||||
child_path = os.path.abspath(child_path)
|
||||
|
||||
# Compare the common path of the parent and child path with the common path of just the parent path. Using the commonpath method on just the parent path will regularise the path name in the same way as the comparison that deals with both paths, removing any trailing path separator
|
||||
# Compare the common path of the parent and child path with the common path of just the parent path.
|
||||
# Using the commonpath method on just the parent path will regularise the path name in the same way
|
||||
# as the comparison that deals with both paths, removing any trailing path separator
|
||||
return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])
|
||||
|
||||
@staticmethod
|
||||
@ -658,7 +839,7 @@ class Helpers:
|
||||
@staticmethod
|
||||
def copy_files(source, dest):
|
||||
if os.path.isfile(source):
|
||||
shutil.copyfile(source, dest)
|
||||
file_helper.copy_file(source, dest)
|
||||
logger.info("Copying jar %s to %s", source, dest)
|
||||
else:
|
||||
logger.info("Source jar does not exist.")
|
||||
@ -688,4 +869,13 @@ class Helpers:
|
||||
return text[len(prefix):]
|
||||
return text
|
||||
|
||||
@staticmethod
|
||||
def getLangPage(text):
|
||||
lang = text.split("_")[0]
|
||||
region = text.split("_")[1]
|
||||
if region == 'EN':
|
||||
return 'en'
|
||||
else:
|
||||
return lang+"-"+region
|
||||
|
||||
helper = Helpers()
|
||||
|
@ -1,7 +1,6 @@
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
|
||||
class install:
|
||||
|
||||
@staticmethod
|
||||
@ -21,5 +20,4 @@ class install:
|
||||
print("Crafty has installed it's dependencies, please restart Crafty")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
installer = install()
|
||||
installer = install()
|
||||
|
@ -1,32 +1,36 @@
|
||||
import os
|
||||
import pathlib
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
from typing import Union
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
#Importing Models
|
||||
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
|
||||
from app.classes.models.servers import servers_helper
|
||||
#Importing Controllers
|
||||
from app.classes.controllers.crafty_perms_controller import Crafty_Perms_Controller
|
||||
from app.classes.controllers.management_controller import Management_Controller
|
||||
from app.classes.controllers.users_controller import Users_Controller
|
||||
from app.classes.controllers.roles_controller import Roles_Controller
|
||||
from app.classes.controllers.server_perms_controller import Server_Perms_Controller
|
||||
from app.classes.controllers.servers_controller import Servers_Controller
|
||||
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.models.users import helper_users
|
||||
from app.classes.models.management import helpers_management
|
||||
from app.classes.models.servers import servers_helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.shared.file_helpers import file_helper
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
try:
|
||||
from peewee import DoesNotExist
|
||||
|
||||
except ModuleNotFoundError as err:
|
||||
helper.auto_installer_fix(err)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -44,7 +48,7 @@ class Controller:
|
||||
|
||||
def check_server_loaded(self, server_id_to_check: int):
|
||||
|
||||
logger.info("Checking to see if we already registered {}".format(server_id_to_check))
|
||||
logger.info(f"Checking to see if we already registered {server_id_to_check}")
|
||||
|
||||
for s in self.servers_list:
|
||||
known_server = s.get('server_id')
|
||||
@ -52,7 +56,7 @@ class Controller:
|
||||
return False
|
||||
|
||||
if known_server == server_id_to_check:
|
||||
logger.info('skipping initialization of server {} because it is already loaded'.format(server_id_to_check))
|
||||
logger.info(f'skipping initialization of server {server_id_to_check} because it is already loaded')
|
||||
return True
|
||||
|
||||
return False
|
||||
@ -69,20 +73,18 @@ class Controller:
|
||||
continue
|
||||
|
||||
# if this server path no longer exists - let's warn and bomb out
|
||||
if not helper.check_path_exists(s['path']):
|
||||
logger.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'],
|
||||
s['path']))
|
||||
if not helper.check_path_exists(helper.get_os_understandable_path(s['path'])):
|
||||
logger.warning(f"Unable to find server {s['server_name']} at path {s['path']}. Skipping this server")
|
||||
|
||||
console.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'],
|
||||
s['path']))
|
||||
console.warning(f"Unable to find server {s['server_name']} at path {s['path']}. Skipping this server")
|
||||
continue
|
||||
|
||||
settings_file = os.path.join(s['path'], 'server.properties')
|
||||
settings_file = os.path.join(helper.get_os_understandable_path(s['path']), 'server.properties')
|
||||
|
||||
# if the properties file isn't there, let's warn
|
||||
if not helper.check_file_exists(settings_file):
|
||||
logger.error("Unable to find {}. Skipping this server.".format(settings_file))
|
||||
console.error("Unable to find {}. Skipping this server.".format(settings_file))
|
||||
logger.error(f"Unable to find {settings_file}. Skipping this server.")
|
||||
console.error(f"Unable to find {settings_file}. Skipping this server.")
|
||||
continue
|
||||
|
||||
settings = ServerProps(settings_file)
|
||||
@ -105,39 +107,103 @@ class Controller:
|
||||
|
||||
self.refresh_server_settings(s['server_id'])
|
||||
|
||||
console.info("Loaded Server: ID {} | Name: {} | Autostart: {} | Delay: {} ".format(
|
||||
s['server_id'],
|
||||
s['server_name'],
|
||||
s['auto_start'],
|
||||
s['auto_start_delay']
|
||||
))
|
||||
console.info(f"Loaded Server: ID {s['server_id']}" +
|
||||
f" | Name: {s['server_name']}" +
|
||||
f" | Autostart: {s['auto_start']}" +
|
||||
f" | Delay: {s['auto_start_delay']} ")
|
||||
|
||||
def refresh_server_settings(self, server_id: int):
|
||||
server_obj = self.get_server_obj(server_id)
|
||||
server_obj.reload_server_settings()
|
||||
|
||||
@staticmethod
|
||||
def check_system_user():
|
||||
if helper_users.get_user_id_by_name("system") is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_project_root(self, root_dir):
|
||||
self.project_root = root_dir
|
||||
|
||||
|
||||
def package_support_logs(self, exec_user):
|
||||
#pausing so on screen notifications can run for user
|
||||
time.sleep(7)
|
||||
websocket_helper.broadcast_user(exec_user['user_id'], 'notification', 'Preparing your support logs')
|
||||
tempDir = tempfile.mkdtemp()
|
||||
tempZipStorage = tempfile.mkdtemp()
|
||||
full_temp = os.path.join(tempDir, 'support_logs')
|
||||
os.mkdir(full_temp)
|
||||
tempZipStorage = os.path.join(tempZipStorage, "support_logs")
|
||||
crafty_path = os.path.join(full_temp, "crafty")
|
||||
os.mkdir(crafty_path)
|
||||
server_path = os.path.join(full_temp, "server")
|
||||
os.mkdir(server_path)
|
||||
if exec_user['superuser']:
|
||||
auth_servers = self.servers.get_all_defined_servers()
|
||||
else:
|
||||
user_servers = self.servers.get_authorized_servers(int(exec_user['user_id']))
|
||||
auth_servers = []
|
||||
for server in user_servers:
|
||||
if Enum_Permissions_Server.Logs in self.server_perms.get_user_id_permissions_list(exec_user['user_id'], server["server_id"]):
|
||||
auth_servers.append(server)
|
||||
else:
|
||||
logger.info(f"Logs permission not available for server {server['server_name']}. Skipping.")
|
||||
#we'll iterate through our list of log paths from auth servers.
|
||||
for server in auth_servers:
|
||||
final_path = os.path.join(server_path, str(server['server_name']))
|
||||
os.mkdir(final_path)
|
||||
try:
|
||||
file_helper.copy_file(server['log_path'], final_path)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to copy file with error: {e}")
|
||||
#Copy crafty logs to archive dir
|
||||
full_log_name = os.path.join(crafty_path, 'logs')
|
||||
file_helper.copy_dir(os.path.join(self.project_root, 'logs'), full_log_name)
|
||||
file_helper.make_archive(tempZipStorage, tempDir)
|
||||
|
||||
tempZipStorage += '.zip'
|
||||
websocket_helper.broadcast_user(exec_user['user_id'], 'send_logs_bootbox', {
|
||||
})
|
||||
|
||||
self.users.set_support_path(exec_user['user_id'], tempZipStorage)
|
||||
|
||||
@staticmethod
|
||||
def add_system_user():
|
||||
helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", False, False)
|
||||
|
||||
def get_server_settings(self, server_id):
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
return s['server_settings']
|
||||
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
logger.warning(f"Unable to find server object for server id {server_id}")
|
||||
return False
|
||||
|
||||
def get_server_obj(self, server_id):
|
||||
def crash_detection(self, server_obj):
|
||||
svr = self.get_server_obj(server_obj.server_id)
|
||||
#start or stop crash detection depending upon user preference
|
||||
#The below functions check to see if the server is running. They only execute if it's running.
|
||||
if server_obj.crash_detection == 1:
|
||||
svr.start_crash_detection()
|
||||
else:
|
||||
svr.stop_crash_detection()
|
||||
|
||||
def get_server_obj(self, server_id: Union[str, int]) -> Union[bool, Server]:
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
if str(s['server_id']) == str(server_id):
|
||||
return s['server_obj']
|
||||
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
return False
|
||||
logger.warning(f"Unable to find server object for server id {server_id}")
|
||||
return False # TODO: Change to None
|
||||
|
||||
def get_server_data(self, server_id):
|
||||
def get_server_data(self, server_id: str):
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
if str(s['server_id']) == str(server_id):
|
||||
return s['server_data_obj']
|
||||
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
logger.warning(f"Unable to find server object for server id {server_id}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@ -165,15 +231,15 @@ class Controller:
|
||||
|
||||
def stop_all_servers(self):
|
||||
servers = self.list_running_servers()
|
||||
logger.info("Found {} running server(s)".format(len(servers)))
|
||||
console.info("Found {} running server(s)".format(len(servers)))
|
||||
logger.info(f"Found {len(servers)} running server(s)")
|
||||
console.info(f"Found {len(servers)} running server(s)")
|
||||
|
||||
logger.info("Stopping All Servers")
|
||||
console.info("Stopping All Servers")
|
||||
|
||||
for s in servers:
|
||||
logger.info("Stopping Server ID {} - {}".format(s['id'], s['name']))
|
||||
console.info("Stopping Server ID {} - {}".format(s['id'], s['name']))
|
||||
logger.info(f"Stopping Server ID {s['id']} - {s['name']}")
|
||||
console.info(f"Stopping Server ID {s['id']} - {s['name']}")
|
||||
|
||||
self.stop_server(s['id'])
|
||||
|
||||
@ -185,14 +251,20 @@ class Controller:
|
||||
|
||||
def stop_server(self, server_id):
|
||||
# issue the stop command
|
||||
svr_obj = self.get_server_obj(server_id)
|
||||
svr_obj.stop_threaded_server()
|
||||
|
||||
def create_jar_server(self, server: str, version: str, name: str, min_mem: int, max_mem: int, port: int):
|
||||
server_id = helper.create_uuid()
|
||||
server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
backup_path = os.path.join(helper.backup_path, server_id)
|
||||
if helper.is_os_windows():
|
||||
server_dir = helper.wtol_path(server_dir)
|
||||
backup_path = helper.wtol_path(backup_path)
|
||||
server_dir.replace(' ', '^ ')
|
||||
backup_path.replace(' ', '^ ')
|
||||
|
||||
server_file = "{server}-{version}.jar".format(server=server, version=version)
|
||||
server_file = f"{server}-{version}.jar"
|
||||
full_jar_path = os.path.join(server_dir, server_file)
|
||||
|
||||
# make the dir - perhaps a UUID?
|
||||
@ -201,33 +273,38 @@ class Controller:
|
||||
|
||||
try:
|
||||
# do a eula.txt
|
||||
with open(os.path.join(server_dir, "eula.txt"), 'w') as f:
|
||||
with open(os.path.join(server_dir, "eula.txt"), 'w', encoding='utf-8') as f:
|
||||
f.write("eula=false")
|
||||
f.close()
|
||||
|
||||
# setup server.properties with the port
|
||||
with open(os.path.join(server_dir, "server.properties"), "w") as f:
|
||||
f.write("server-port={}".format(port))
|
||||
with open(os.path.join(server_dir, "server.properties"), "w", encoding='utf-8') as f:
|
||||
f.write(f"server-port={port}")
|
||||
f.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to create required server files due to :{}".format(e))
|
||||
logger.error(f"Unable to create required server files due to :{e}")
|
||||
return False
|
||||
|
||||
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
|
||||
helper.float_to_string(max_mem),
|
||||
full_jar_path)
|
||||
server_log_file = "{}/logs/latest.log".format(server_dir)
|
||||
#must remain non-fstring due to string addtion
|
||||
if helper.is_os_windows():
|
||||
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar "{full_jar_path}" nogui'
|
||||
else:
|
||||
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar {full_jar_path} nogui'
|
||||
server_log_file = f"{server_dir}/logs/latest.log"
|
||||
server_stop = "stop"
|
||||
|
||||
# download the jar
|
||||
server_jar_obj.download_jar(server, version, full_jar_path, name)
|
||||
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop,
|
||||
port, server_type='minecraft-java')
|
||||
|
||||
# download the jar
|
||||
server_jar_obj.download_jar(server, version, full_jar_path, new_id)
|
||||
|
||||
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop)
|
||||
return new_id
|
||||
|
||||
@staticmethod
|
||||
def verify_jar_server( server_path: str, server_jar: str):
|
||||
server_path = helper.get_os_understandable_path(server_path)
|
||||
path_check = helper.check_path_exists(server_path)
|
||||
jar_check = helper.check_file_exists(os.path.join(server_path, server_jar))
|
||||
if not path_check or not jar_check:
|
||||
@ -236,6 +313,7 @@ class Controller:
|
||||
|
||||
@staticmethod
|
||||
def verify_zip_server(zip_path: str):
|
||||
zip_path = helper.get_os_understandable_path(zip_path)
|
||||
zip_check = helper.check_file_exists(zip_path)
|
||||
if not zip_check:
|
||||
return False
|
||||
@ -245,88 +323,239 @@ class Controller:
|
||||
server_id = helper.create_uuid()
|
||||
new_server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
backup_path = os.path.join(helper.backup_path, server_id)
|
||||
if helper.is_os_windows():
|
||||
new_server_dir = helper.wtol_path(new_server_dir)
|
||||
backup_path = helper.wtol_path(backup_path)
|
||||
new_server_dir.replace(' ', '^ ')
|
||||
backup_path.replace(' ', '^ ')
|
||||
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
dir_util.copy_tree(server_path, new_server_dir)
|
||||
server_path = helper.get_os_understandable_path(server_path)
|
||||
try:
|
||||
file_helper.copy_dir(server_path, new_server_dir, True)
|
||||
except shutil.Error as ex:
|
||||
logger.error(f"Server import failed with error: {ex}")
|
||||
|
||||
has_properties = False
|
||||
for item in os.listdir(new_server_dir):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
if not has_properties:
|
||||
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
|
||||
f.write(f"server-port={port}")
|
||||
f.close()
|
||||
|
||||
full_jar_path = os.path.join(new_server_dir, server_jar)
|
||||
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
|
||||
helper.float_to_string(max_mem),
|
||||
full_jar_path)
|
||||
server_log_file = "{}/logs/latest.log".format(new_server_dir)
|
||||
|
||||
#due to adding strings this must not be an fstring
|
||||
if helper.is_os_windows():
|
||||
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar "{full_jar_path}" nogui'
|
||||
else:
|
||||
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar {full_jar_path} nogui'
|
||||
server_log_file = f"{new_server_dir}/logs/latest.log"
|
||||
server_stop = "stop"
|
||||
|
||||
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
|
||||
server_log_file, server_stop, port)
|
||||
server_log_file, server_stop, port, server_type='minecraft-java')
|
||||
return new_id
|
||||
|
||||
def import_zip_server(self, server_name: str, zip_path: str, server_jar: str, min_mem: int, max_mem: int, port: int):
|
||||
server_id = helper.create_uuid()
|
||||
new_server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
backup_path = os.path.join(helper.backup_path, server_id)
|
||||
if helper.is_os_windows():
|
||||
new_server_dir = helper.wtol_path(new_server_dir)
|
||||
backup_path = helper.wtol_path(backup_path)
|
||||
new_server_dir.replace(' ', '^ ')
|
||||
backup_path.replace(' ', '^ ')
|
||||
|
||||
if helper.check_file_perms(zip_path):
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(tempDir)
|
||||
for i in range(len(zip_ref.filelist)):
|
||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
|
||||
test = zip_ref.filelist[i].filename
|
||||
break
|
||||
path_list = test.split('/')
|
||||
root_path = path_list[0]
|
||||
if len(path_list) > 1:
|
||||
for i in range(len(path_list)-2):
|
||||
root_path = os.path.join(root_path, path_list[i+1])
|
||||
|
||||
full_root_path = os.path.join(tempDir, root_path)
|
||||
|
||||
has_properties = False
|
||||
for item in os.listdir(full_root_path):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
try:
|
||||
shutil.move(os.path.join(full_root_path, item), os.path.join(new_server_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
|
||||
if not has_properties:
|
||||
logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port)))
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w") as f:
|
||||
f.write("server-port={}".format(port))
|
||||
f.close()
|
||||
zip_ref.close()
|
||||
else:
|
||||
return "false"
|
||||
tempDir = helper.get_os_understandable_path(zip_path)
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
has_properties = False
|
||||
#extracts archive to temp directory
|
||||
for item in os.listdir(tempDir):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
try:
|
||||
if not os.path.isdir(os.path.join(tempDir, item)):
|
||||
file_helper.move_file(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
|
||||
else:
|
||||
file_helper.move_dir(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error(f'ERROR IN ZIP IMPORT: {ex}')
|
||||
if not has_properties:
|
||||
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
|
||||
f.write(f"server-port={port}")
|
||||
f.close()
|
||||
|
||||
full_jar_path = os.path.join(new_server_dir, server_jar)
|
||||
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
|
||||
helper.float_to_string(max_mem),
|
||||
full_jar_path)
|
||||
|
||||
#due to strings being added we need to leave this as not an fstring
|
||||
if helper.is_os_windows():
|
||||
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar "{full_jar_path}" nogui'
|
||||
else:
|
||||
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar {full_jar_path} nogui'
|
||||
logger.debug('command: ' + server_command)
|
||||
server_log_file = "{}/logs/latest.log".format(new_server_dir)
|
||||
server_log_file = f"{new_server_dir}/logs/latest.log"
|
||||
server_stop = "stop"
|
||||
|
||||
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
|
||||
server_log_file, server_stop, port)
|
||||
server_log_file, server_stop, port, server_type='minecraft-java')
|
||||
return new_id
|
||||
|
||||
def register_server(self, name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
# put data in the db
|
||||
new_id = self.servers.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
|
||||
#************************************************************************************************
|
||||
# BEDROCK IMPORTS
|
||||
#************************************************************************************************
|
||||
|
||||
def import_bedrock_server(self, server_name: str, server_path: str, server_exe: str, port: int):
|
||||
server_id = helper.create_uuid()
|
||||
new_server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
backup_path = os.path.join(helper.backup_path, server_id)
|
||||
if helper.is_os_windows():
|
||||
new_server_dir = helper.wtol_path(new_server_dir)
|
||||
backup_path = helper.wtol_path(backup_path)
|
||||
new_server_dir.replace(' ', '^ ')
|
||||
backup_path.replace(' ', '^ ')
|
||||
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
server_path = helper.get_os_understandable_path(server_path)
|
||||
try:
|
||||
# place a file in the dir saying it's owned by crafty
|
||||
with open(os.path.join(server_dir, "crafty_managed.txt"), 'w') as f:
|
||||
f.write(
|
||||
"The server is managed by Crafty Controller.\n Leave this directory/files alone please")
|
||||
file_helper.copy_dir(server_path, new_server_dir, True)
|
||||
except shutil.Error as ex:
|
||||
logger.error(f"Server import failed with error: {ex}")
|
||||
|
||||
has_properties = False
|
||||
for item in os.listdir(new_server_dir):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
if not has_properties:
|
||||
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
|
||||
f.write(f"server-port={port}")
|
||||
f.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to create required server files due to :{}".format(e))
|
||||
return False
|
||||
full_jar_path = os.path.join(new_server_dir, server_exe)
|
||||
|
||||
#due to adding strings this must not be an fstring
|
||||
if helper.is_os_windows():
|
||||
server_command = f'"{full_jar_path}"'
|
||||
else:
|
||||
server_command = f'./{server_exe}'
|
||||
logger.debug('command: ' + server_command)
|
||||
server_log_file = "N/A"
|
||||
server_stop = "stop"
|
||||
|
||||
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
|
||||
server_log_file, server_stop, port, server_type='minecraft-bedrock')
|
||||
if os.name != "nt":
|
||||
if helper.check_file_exists(full_jar_path):
|
||||
os.chmod(full_jar_path, 0o2775)
|
||||
return new_id
|
||||
|
||||
def import_bedrock_zip_server(self, server_name: str, zip_path: str, server_exe: str, port: int):
|
||||
server_id = helper.create_uuid()
|
||||
new_server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
backup_path = os.path.join(helper.backup_path, server_id)
|
||||
if helper.is_os_windows():
|
||||
new_server_dir = helper.wtol_path(new_server_dir)
|
||||
backup_path = helper.wtol_path(backup_path)
|
||||
new_server_dir.replace(' ', '^ ')
|
||||
backup_path.replace(' ', '^ ')
|
||||
|
||||
tempDir = helper.get_os_understandable_path(zip_path)
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
has_properties = False
|
||||
#extracts archive to temp directory
|
||||
for item in os.listdir(tempDir):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
try:
|
||||
if not os.path.isdir(os.path.join(tempDir, item)):
|
||||
file_helper.move_file(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
|
||||
else:
|
||||
file_helper.move_dir(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error(f'ERROR IN ZIP IMPORT: {ex}')
|
||||
if not has_properties:
|
||||
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
|
||||
f.write(f"server-port={port}")
|
||||
f.close()
|
||||
|
||||
full_jar_path = os.path.join(new_server_dir, server_exe)
|
||||
|
||||
#due to strings being added we need to leave this as not an fstring
|
||||
if helper.is_os_windows():
|
||||
server_command = f'"{full_jar_path}"'
|
||||
else:
|
||||
server_command = f'./{server_exe}'
|
||||
logger.debug('command: ' + server_command)
|
||||
server_log_file = "N/A"
|
||||
server_stop = "stop"
|
||||
|
||||
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
|
||||
server_log_file, server_stop, port, server_type='minecraft-bedrock')
|
||||
if os.name != "nt":
|
||||
if helper.check_file_exists(full_jar_path):
|
||||
os.chmod(full_jar_path, 0o2775)
|
||||
|
||||
return new_id
|
||||
|
||||
#************************************************************************************************
|
||||
# BEDROCK IMPORTS END
|
||||
#************************************************************************************************
|
||||
|
||||
def rename_backup_dir(self, old_server_id, new_server_id, new_uuid):
|
||||
server_data = self.servers.get_server_data_by_id(old_server_id)
|
||||
old_bu_path = server_data['backup_path']
|
||||
Server_Perms_Controller.backup_role_swap(old_server_id, new_server_id)
|
||||
if not helper.is_os_windows():
|
||||
backup_path = helper.validate_traversal(helper.backup_path, old_bu_path)
|
||||
if helper.is_os_windows():
|
||||
backup_path = helper.validate_traversal(helper.wtol_path(helper.backup_path), helper.wtol_path(old_bu_path))
|
||||
backup_path = helper.wtol_path(str(backup_path))
|
||||
backup_path.replace(' ', '^ ')
|
||||
backup_path = Path(backup_path)
|
||||
backup_path_components = list(backup_path.parts)
|
||||
backup_path_components[-1] = new_uuid
|
||||
new_bu_path = pathlib.PurePath(os.path.join(*backup_path_components))
|
||||
if os.path.isdir(new_bu_path):
|
||||
if helper.validate_traversal(helper.backup_path, new_bu_path):
|
||||
os.rmdir(new_bu_path)
|
||||
backup_path.rename(new_bu_path)
|
||||
|
||||
def register_server(self, name: str,
|
||||
server_uuid: str,
|
||||
server_dir: str,
|
||||
backup_path: str,
|
||||
server_command: str,
|
||||
server_file: str,
|
||||
server_log_file: str,
|
||||
server_stop: str,
|
||||
server_port: int,
|
||||
server_type: str):
|
||||
# put data in the db
|
||||
|
||||
new_id = self.servers.create_server(
|
||||
name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_type, server_port)
|
||||
|
||||
if not helper.check_file_exists(os.path.join(server_dir, "crafty_managed.txt")):
|
||||
try:
|
||||
# place a file in the dir saying it's owned by crafty
|
||||
with open(os.path.join(server_dir, "crafty_managed.txt"), 'w', encoding='utf-8') as f:
|
||||
f.write(
|
||||
"The server is managed by Crafty Controller.\n Leave this directory/files alone please")
|
||||
f.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to create required server files due to :{e}")
|
||||
return False
|
||||
|
||||
# let's re-init all servers
|
||||
self.init_all_servers()
|
||||
@ -338,12 +567,12 @@ class Controller:
|
||||
for s in self.servers_list:
|
||||
|
||||
# if this is the droid... im mean server we are looking for...
|
||||
if int(s['server_id']) == int(server_id):
|
||||
if str(s['server_id']) == str(server_id):
|
||||
server_data = self.get_server_data(server_id)
|
||||
server_name = server_data['server_name']
|
||||
|
||||
logger.info("Deleting Server: ID {} | Name: {} ".format(server_id, server_name))
|
||||
console.info("Deleting Server: ID {} | Name: {} ".format(server_id, server_name))
|
||||
logger.info(f"Deleting Server: ID {server_id} | Name: {server_name} ")
|
||||
console.info(f"Deleting Server: ID {server_id} | Name: {server_name} ")
|
||||
|
||||
srv_obj = s['server_obj']
|
||||
running = srv_obj.check_running()
|
||||
@ -351,7 +580,19 @@ class Controller:
|
||||
if running:
|
||||
self.stop_server(server_id)
|
||||
if files:
|
||||
shutil.rmtree(self.servers.get_server_data_by_id(server_id)['path'])
|
||||
try:
|
||||
file_helper.del_dirs(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['path']))
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to delete server files for server with ID: {server_id} with error logged: {e}")
|
||||
if helper.check_path_exists(self.servers.get_server_data_by_id(server_id)['backup_path']):
|
||||
file_helper.del_dirs(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['backup_path']))
|
||||
|
||||
|
||||
#Cleanup scheduled tasks
|
||||
try:
|
||||
helpers_management.delete_scheduled_task_by_server(server_id)
|
||||
except DoesNotExist:
|
||||
logger.info("No scheduled jobs exist. Continuing.")
|
||||
# remove the server from the DB
|
||||
self.servers.remove_server(server_id)
|
||||
|
||||
@ -359,3 +600,6 @@ class Controller:
|
||||
self.servers_list.pop(counter)
|
||||
|
||||
counter += 1
|
||||
@staticmethod
|
||||
def clear_unexecuted_commands():
|
||||
helpers_management.clear_unexecuted_commands()
|
||||
|
@ -1,31 +1,25 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.models.users import Users, users_helper
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.users import Users, users_helper
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
# To disable warning about unused import ; Users is imported from here in other places
|
||||
# pylint: disable=self-assigning-variable
|
||||
Users = Users
|
||||
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
from peewee import SqliteDatabase, fn
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
except ModuleNotFoundError as err:
|
||||
helper.auto_installer_fix(err)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
database = SqliteDatabase(helper.db_path, pragmas = {
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
@ -39,28 +33,17 @@ class db_builder:
|
||||
|
||||
username = default_data.get("username", 'admin')
|
||||
password = default_data.get("password", 'crafty')
|
||||
#api_token = helper.random_string_generator(32)
|
||||
#
|
||||
#Users.insert({
|
||||
# Users.username: username.lower(),
|
||||
# Users.password: helper.encode_pass(password),
|
||||
# Users.api_token: api_token,
|
||||
# Users.enabled: True,
|
||||
# Users.superuser: True
|
||||
#}).execute()
|
||||
user_id = users_helper.add_user(username=username, password=password, superuser=True)
|
||||
#users_helper.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} )
|
||||
|
||||
#console.info("API token is {}".format(api_token))
|
||||
users_helper.add_user(username=username, password=password, email="default@example.com", superuser=True)
|
||||
|
||||
@staticmethod
|
||||
def is_fresh_install():
|
||||
try:
|
||||
user = users_helper.get_by_id(1)
|
||||
return False
|
||||
if user:
|
||||
return False
|
||||
except:
|
||||
return True
|
||||
pass
|
||||
|
||||
class db_shortcuts:
|
||||
|
||||
@ -76,8 +59,7 @@ class db_shortcuts:
|
||||
for s in query:
|
||||
rows.append(model_to_dict(s))
|
||||
except Exception as e:
|
||||
logger.warning("Database Error: {}".format(e))
|
||||
pass
|
||||
logger.warning(f"Database Error: {e}")
|
||||
|
||||
return rows
|
||||
|
||||
@ -85,10 +67,10 @@ class db_shortcuts:
|
||||
def return_db_rows(model):
|
||||
data = [model_to_dict(row) for row in model]
|
||||
return data
|
||||
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Static Accessors
|
||||
# Static Accessors
|
||||
#************************************************************************************************
|
||||
installer = db_builder()
|
||||
db_helper = db_shortcuts()
|
||||
|
@ -1,536 +1,447 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import typing as t
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from importlib import import_module
|
||||
from functools import wraps
|
||||
|
||||
try:
|
||||
from functools import cached_property
|
||||
except ImportError:
|
||||
from cached_property import cached_property
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import peewee
|
||||
from playhouse.migrate import (
|
||||
SchemaMigrator as ScM,
|
||||
SqliteMigrator as SqM,
|
||||
Operation, SQL, operation, SqliteDatabase,
|
||||
make_index_name, Context
|
||||
)
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(
|
||||
e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class MigrateHistory(peewee.Model):
|
||||
"""
|
||||
Presents the migration history in a database.
|
||||
"""
|
||||
|
||||
name = peewee.CharField(unique=True)
|
||||
migrated_at = peewee.DateTimeField(default=datetime.utcnow)
|
||||
|
||||
def __unicode__(self) -> str:
|
||||
"""
|
||||
String representation of this migration
|
||||
"""
|
||||
return self.name
|
||||
|
||||
|
||||
MIGRATE_TABLE = 'migratehistory'
|
||||
MIGRATE_TEMPLATE = '''# Generated by database migrator
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
{migrate}
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
||||
{rollback}'''
|
||||
VOID: t.Callable = lambda m, d: None
|
||||
|
||||
|
||||
def get_model(method):
|
||||
"""
|
||||
Convert string to model class.
|
||||
"""
|
||||
|
||||
@wraps(method)
|
||||
def wrapper(migrator, model, *args, **kwargs):
|
||||
if isinstance(model, str):
|
||||
return method(migrator, migrator.orm[model], *args, **kwargs)
|
||||
return method(migrator, model, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class Migrator(object):
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migrator
|
||||
"""
|
||||
if isinstance(database, peewee.Proxy):
|
||||
database = database.obj
|
||||
self.database: SqliteDatabase = database
|
||||
self.orm: t.Dict[str, peewee.Model] = {}
|
||||
self.operations: t.List[Operation] = []
|
||||
self.migrator = SqliteMigrator(database)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs operations.
|
||||
"""
|
||||
for op in self.operations:
|
||||
if isinstance(op, Operation):
|
||||
op.run()
|
||||
else:
|
||||
op()
|
||||
self.clean()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Cleans the operations.
|
||||
"""
|
||||
self.operations = list()
|
||||
|
||||
def sql(self, sql: str, *params):
|
||||
"""
|
||||
Executes raw SQL.
|
||||
"""
|
||||
self.operations.append(self.migrator.sql(sql, *params))
|
||||
|
||||
def create_table(self, model: peewee.Model) -> peewee.Model:
|
||||
"""
|
||||
Creates model and table in database.
|
||||
"""
|
||||
self.orm[model._meta.table_name] = model
|
||||
model._meta.database = self.database
|
||||
self.operations.append(model.create_table)
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_table(self, model: peewee.Model):
|
||||
"""
|
||||
Drops model and table from database.
|
||||
"""
|
||||
del self.orm[model._meta.table_name]
|
||||
self.operations.append(self.migrator.drop_table(model))
|
||||
|
||||
@get_model
|
||||
def add_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
|
||||
"""
|
||||
Creates new fields.
|
||||
"""
|
||||
for name, field in fields.items():
|
||||
model._meta.add_field(name, field)
|
||||
self.operations.append(self.migrator.add_column(
|
||||
model._meta.table_name, field.column_name, field))
|
||||
if field.unique:
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, (field.column_name,), unique=True))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def change_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
|
||||
"""
|
||||
Changes fields.
|
||||
"""
|
||||
for name, field in fields.items():
|
||||
old_field = model._meta.fields.get(name, field)
|
||||
old_column_name = old_field and old_field.column_name
|
||||
|
||||
model._meta.add_field(name, field)
|
||||
|
||||
if isinstance(old_field, peewee.ForeignKeyField):
|
||||
self.operations.append(self.migrator.drop_foreign_key_constraint(
|
||||
model._meta.table_name, old_column_name))
|
||||
|
||||
if old_column_name != field.column_name:
|
||||
self.operations.append(
|
||||
self.migrator.rename_column(
|
||||
model._meta.table_name, old_column_name, field.column_name))
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
on_delete = field.on_delete if field.on_delete else 'RESTRICT'
|
||||
on_update = field.on_update if field.on_update else 'RESTRICT'
|
||||
self.operations.append(self.migrator.add_foreign_key_constraint(
|
||||
model._meta.table_name, field.column_name,
|
||||
field.rel_model._meta.table_name, field.rel_field.name,
|
||||
on_delete, on_update))
|
||||
continue
|
||||
|
||||
self.operations.append(self.migrator.change_column(
|
||||
model._meta.table_name, field.column_name, field))
|
||||
|
||||
if field.unique == old_field.unique:
|
||||
continue
|
||||
|
||||
if field.unique:
|
||||
index = (field.column_name,), field.unique
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, *index))
|
||||
model._meta.indexes.append(index)
|
||||
else:
|
||||
index = (field.column_name,), old_field.unique
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, *index))
|
||||
model._meta.indexes.remove(index)
|
||||
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_columns(self, model: peewee.Model, names: str, **kwargs) -> peewee.Model:
|
||||
"""
|
||||
Removes fields from model.
|
||||
"""
|
||||
fields = [field for field in model._meta.fields.values()
|
||||
if field.name in names]
|
||||
cascade = kwargs.pop('cascade', True)
|
||||
for field in fields:
|
||||
self.__del_field__(model, field)
|
||||
if field.unique:
|
||||
index_name = make_index_name(
|
||||
model._meta.table_name, [field.column_name])
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
self.operations.append(
|
||||
self.migrator.drop_column(
|
||||
model._meta.table_name, field.column_name, cascade=False))
|
||||
return model
|
||||
|
||||
def __del_field__(self, model: peewee.Model, field: peewee.Field):
|
||||
"""
|
||||
Deletes field from model.
|
||||
"""
|
||||
model._meta.remove_field(field.name)
|
||||
delattr(model, field.name)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
obj_id_name = field.column_name
|
||||
if field.column_name == field.name:
|
||||
obj_id_name += '_id'
|
||||
delattr(model, obj_id_name)
|
||||
delattr(field.rel_model, field.backref)
|
||||
|
||||
@get_model
|
||||
def rename_column(self, model: peewee.Model, old_name: str, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames field in model.
|
||||
"""
|
||||
field = model._meta.fields[old_name]
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
old_name = field.column_name
|
||||
self.__del_field__(model, field)
|
||||
field.name = field.column_name = new_name
|
||||
model._meta.add_field(new_name, field)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
field.column_name = new_name = field.column_name + '_id'
|
||||
self.operations.append(self.migrator.rename_column(
|
||||
model._meta.table_name, old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def rename_table(self, model: peewee.Model, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames table in database.
|
||||
"""
|
||||
old_name = model._meta.table_name
|
||||
del self.orm[model._meta.table_name]
|
||||
model._meta.table_name = new_name
|
||||
self.orm[model._meta.table_name] = model
|
||||
self.operations.append(self.migrator.rename_table(old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_index(self, model: peewee.Model, *columns: str, **kwargs) -> peewee.Model:
|
||||
"""Create indexes."""
|
||||
unique = kwargs.pop('unique', False)
|
||||
model._meta.indexes.append((columns, unique))
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = unique
|
||||
field.index = not unique
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
|
||||
columns_.append(col)
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, columns_, unique=unique))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_index(self, model: peewee.Model, *columns: str) -> peewee.Model:
|
||||
"""Drop indexes."""
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
if not field:
|
||||
continue
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = field.index = False
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
columns_.append(col)
|
||||
index_name = make_index_name(model._meta.table_name, columns_)
|
||||
model._meta.indexes = [(cols, _) for (
|
||||
cols, _) in model._meta.indexes if columns != cols]
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Add not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = False
|
||||
self.operations.append(self.migrator.add_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Drop not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = True
|
||||
self.operations.append(self.migrator.drop_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_default(self, model: peewee.Model, name: str, default: t.Any) -> peewee.Model:
|
||||
"""Add default."""
|
||||
field = model._meta.fields[name]
|
||||
model._meta.defaults[field] = field.default = default
|
||||
self.operations.append(self.migrator.apply_default(
|
||||
model._meta.table_name, name, field))
|
||||
return model
|
||||
|
||||
|
||||
class SqliteMigrator(SqM):
|
||||
def drop_table(self, model):
|
||||
return lambda: model.drop_table(cascade=False)
|
||||
|
||||
@operation
|
||||
def change_column(self, table: str, column_name: str, field: peewee.Field):
|
||||
operations = [self.alter_change_column(table, column_name, field)]
|
||||
if not field.null:
|
||||
operations.extend([self.add_not_null(table, column_name)])
|
||||
return operations
|
||||
|
||||
def alter_change_column(self, table: str, column_name: str, field: peewee.Field) -> Operation:
|
||||
return self._update_column(table, column_name, lambda x, y: y)
|
||||
|
||||
@operation
|
||||
def sql(self, sql: str, *params) -> SQL:
|
||||
"""
|
||||
Executes raw SQL.
|
||||
"""
|
||||
return SQL(sql, *params)
|
||||
|
||||
def alter_add_column(
|
||||
self, table: str, column_name: str, field: peewee.Field, **kwargs) -> Operation:
|
||||
"""
|
||||
Fixes field name for ForeignKeys.
|
||||
"""
|
||||
name = field.name
|
||||
op = super().alter_add_column(
|
||||
table, column_name, field, **kwargs)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
field.name = name
|
||||
return op
|
||||
|
||||
|
||||
class MigrationManager(object):
|
||||
|
||||
filemask = re.compile(r"[\d]+_[^\.]+\.py$")
|
||||
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migration manager.
|
||||
"""
|
||||
if not isinstance(database, (peewee.Database, peewee.Proxy)):
|
||||
raise RuntimeError('Invalid database: {}'.format(database))
|
||||
self.database = database
|
||||
|
||||
@cached_property
|
||||
def model(self) -> peewee.Model:
|
||||
"""
|
||||
Initialize and cache the MigrationHistory model.
|
||||
"""
|
||||
MigrateHistory._meta.database = self.database
|
||||
MigrateHistory._meta.table_name = 'migratehistory'
|
||||
MigrateHistory._meta.schema = None
|
||||
MigrateHistory.create_table(True)
|
||||
return MigrateHistory
|
||||
|
||||
@property
|
||||
def done(self) -> t.List[str]:
|
||||
"""
|
||||
Scans migrations in the database.
|
||||
"""
|
||||
return [mm.name for mm in self.model.select().order_by(self.model.id)]
|
||||
|
||||
@property
|
||||
def todo(self):
|
||||
"""
|
||||
Scans migrations in the file system.
|
||||
"""
|
||||
if not os.path.exists(helper.migration_dir):
|
||||
logger.warning('Migration directory: {} does not exist.'.format(
|
||||
helper.migration_dir))
|
||||
os.makedirs(helper.migration_dir)
|
||||
return sorted(f[:-3] for f in os.listdir(helper.migration_dir) if self.filemask.match(f))
|
||||
|
||||
@property
|
||||
def diff(self) -> t.List[str]:
|
||||
"""
|
||||
Calculates difference between the filesystem and the database.
|
||||
"""
|
||||
done = set(self.done)
|
||||
return [name for name in self.todo if name not in done]
|
||||
|
||||
@cached_property
|
||||
def migrator(self) -> Migrator:
|
||||
"""
|
||||
Create migrator and setup it with fake migrations.
|
||||
"""
|
||||
migrator = Migrator(self.database)
|
||||
for name in self.done:
|
||||
self.up_one(name, migrator, True)
|
||||
return migrator
|
||||
|
||||
def compile(self, name, migrate='', rollback=''):
|
||||
"""
|
||||
Compiles a migration.
|
||||
"""
|
||||
name = datetime.utcnow().strftime('%Y%m%d%H%M%S') + '_' + name
|
||||
filename = name + '.py'
|
||||
path = os.path.join(helper.migration_dir, filename)
|
||||
with open(path, 'w') as f:
|
||||
f.write(MIGRATE_TEMPLATE.format(
|
||||
migrate=migrate, rollback=rollback, name=filename))
|
||||
|
||||
return name
|
||||
|
||||
def create(self, name: str = 'auto', auto: bool = False) -> t.Optional[str]:
|
||||
"""
|
||||
Creates a migration.
|
||||
"""
|
||||
migrate = rollback = ''
|
||||
if auto:
|
||||
raise NotImplementedError
|
||||
|
||||
logger.info('Creating migration "{}"'.format(name))
|
||||
name = self.compile(name, migrate, rollback)
|
||||
logger.info('Migration has been created as "{}"'.format(name))
|
||||
return name
|
||||
|
||||
def clear(self):
|
||||
"""Clear migrations."""
|
||||
self.model.delete().execute()
|
||||
|
||||
def up(self, name: t.Optional[str] = None):
|
||||
"""
|
||||
Runs all unapplied migrations.
|
||||
"""
|
||||
logger.info('Starting migrations')
|
||||
console.info('Starting migrations')
|
||||
|
||||
done = []
|
||||
diff = self.diff
|
||||
if not diff:
|
||||
logger.info('There is nothing to migrate')
|
||||
console.info('There is nothing to migrate')
|
||||
return done
|
||||
|
||||
migrator = self.migrator
|
||||
for mname in diff:
|
||||
done.append(self.up_one(mname, self.migrator))
|
||||
if name and name == mname:
|
||||
break
|
||||
|
||||
return done
|
||||
|
||||
def read(self, name: str):
|
||||
"""
|
||||
Reads a migration from a file.
|
||||
"""
|
||||
call_params = dict()
|
||||
if os.name == 'nt' and sys.version_info >= (3, 0):
|
||||
# if system is windows - force utf-8 encoding
|
||||
call_params['encoding'] = 'utf-8'
|
||||
with open(os.path.join(helper.migration_dir, name + '.py'), **call_params) as f:
|
||||
code = f.read()
|
||||
scope = {}
|
||||
code = compile(code, '<string>', 'exec', dont_inherit=True)
|
||||
exec(code, scope, None)
|
||||
return scope.get('migrate', VOID), scope.get('rollback', VOID)
|
||||
|
||||
def up_one(self, name: str, migrator: Migrator,
|
||||
fake: bool = False, rollback: bool = False) -> str:
|
||||
"""
|
||||
Runs a migration with a given name.
|
||||
"""
|
||||
try:
|
||||
migrate_fn, rollback_fn = self.read(name)
|
||||
if fake:
|
||||
migrate_fn(migrator, self.database)
|
||||
migrator.clean()
|
||||
return name
|
||||
with self.database.transaction():
|
||||
if rollback:
|
||||
logger.info('Rolling back "{}"'.format(name))
|
||||
rollback_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
self.model.delete().where(self.model.name == name).execute()
|
||||
else:
|
||||
logger.info('Migrate "{}"'.format(name))
|
||||
migrate_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
if name not in self.done:
|
||||
self.model.create(name=name)
|
||||
|
||||
logger.info('Done "{}"'.format(name))
|
||||
return name
|
||||
|
||||
except Exception:
|
||||
self.database.rollback()
|
||||
operation = 'Rollback' if rollback else 'Migration'
|
||||
logger.exception('{} failed: {}'.format(operation, name))
|
||||
raise
|
||||
|
||||
def down(self, name: t.Optional[str] = None):
|
||||
"""
|
||||
Rolls back migrations.
|
||||
"""
|
||||
if not self.done:
|
||||
raise RuntimeError('No migrations are found.')
|
||||
|
||||
name = self.done[-1]
|
||||
|
||||
migrator = self.migrator
|
||||
self.up_one(name, migrator, False, True)
|
||||
logger.warning('Rolled back migration: {}'.format(name))
|
||||
# pylint: skip-file
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import typing as t
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from functools import wraps
|
||||
from functools import cached_property
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
try:
|
||||
import peewee
|
||||
from playhouse.migrate import (
|
||||
SqliteMigrator,
|
||||
Operation, SQL, SqliteDatabase,
|
||||
make_index_name
|
||||
)
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MIGRATE_TABLE = 'migratehistory'
|
||||
MIGRATE_TEMPLATE = '''# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, db):
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
{migrate}
|
||||
|
||||
def rollback(migrator, db):
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
||||
{rollback}'''
|
||||
|
||||
|
||||
class MigrateHistory(peewee.Model):
|
||||
"""
|
||||
Presents the migration history in a database.
|
||||
"""
|
||||
|
||||
name = peewee.CharField(unique=True)
|
||||
migrated_at = peewee.DateTimeField(default=datetime.utcnow)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
def __unicode__(self) -> str:
|
||||
"""
|
||||
String representation of this migration
|
||||
"""
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
table_name = MIGRATE_TABLE
|
||||
|
||||
|
||||
def get_model(method):
|
||||
"""
|
||||
Convert string to model class.
|
||||
"""
|
||||
|
||||
@wraps(method)
|
||||
def wrapper(migrator, model, *args, **kwargs):
|
||||
if isinstance(model, str):
|
||||
return method(migrator, migrator.table_dict[model], *args, **kwargs)
|
||||
return method(migrator, model, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
class Migrator(object):
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migrator
|
||||
"""
|
||||
if isinstance(database, peewee.Proxy):
|
||||
database = database.obj
|
||||
self.database: SqliteDatabase = database
|
||||
self.table_dict: t.Dict[str, peewee.Model] = {}
|
||||
self.operations: t.List[t.Union[Operation, callable]] = []
|
||||
self.migrator = SqliteMigrator(database)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs operations.
|
||||
"""
|
||||
for op in self.operations:
|
||||
if isinstance(op, Operation):
|
||||
op.run()
|
||||
else:
|
||||
op()
|
||||
self.clean()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Cleans the operations.
|
||||
"""
|
||||
self.operations = list()
|
||||
|
||||
def sql(self, sql: str, *params):
|
||||
"""
|
||||
Executes raw SQL.
|
||||
"""
|
||||
self.operations.append(SQL(sql, *params))
|
||||
|
||||
def create_table(self, model: peewee.Model) -> peewee.Model:
|
||||
"""
|
||||
Creates model and table in database.
|
||||
"""
|
||||
self.table_dict[model._meta.table_name] = model
|
||||
model._meta.database = self.database
|
||||
self.operations.append(model.create_table)
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_table(self, model: peewee.Model):
|
||||
"""
|
||||
Drops model and table from database.
|
||||
"""
|
||||
del self.table_dict[model._meta.table_name]
|
||||
self.operations.append(lambda: model.drop_table(cascade=False))
|
||||
|
||||
@get_model
|
||||
def add_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
|
||||
"""
|
||||
Creates new fields.
|
||||
"""
|
||||
for name, field in fields.items():
|
||||
model._meta.add_field(name, field)
|
||||
self.operations.append(self.migrator.add_column(
|
||||
model._meta.table_name, field.column_name, field))
|
||||
if field.unique:
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, (field.column_name,), unique=True))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_columns(self, model: peewee.Model, names: str) -> peewee.Model:
|
||||
"""
|
||||
Removes fields from model.
|
||||
"""
|
||||
fields = [field for field in model._meta.fields.values()
|
||||
if field.name in names]
|
||||
for field in fields:
|
||||
self.__del_field__(model, field)
|
||||
if field.unique:
|
||||
# Drop unique index
|
||||
index_name = make_index_name(
|
||||
model._meta.table_name, [field.column_name])
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
self.operations.append(
|
||||
self.migrator.drop_column(
|
||||
model._meta.table_name, field.column_name, cascade=False))
|
||||
return model
|
||||
|
||||
def __del_field__(self, model: peewee.Model, field: peewee.Field):
|
||||
"""
|
||||
Deletes field from model.
|
||||
"""
|
||||
model._meta.remove_field(field.name)
|
||||
delattr(model, field.name)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
obj_id_name = field.column_name
|
||||
if field.column_name == field.name:
|
||||
obj_id_name += '_id'
|
||||
delattr(model, obj_id_name)
|
||||
delattr(field.rel_model, field.backref)
|
||||
|
||||
@get_model
|
||||
def rename_column(self, model: peewee.Model, old_name: str, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames field in model.
|
||||
"""
|
||||
field = model._meta.fields[old_name]
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
old_name = field.column_name
|
||||
self.__del_field__(model, field)
|
||||
field.name = field.column_name = new_name
|
||||
model._meta.add_field(new_name, field)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
field.column_name = new_name = field.column_name + '_id'
|
||||
self.operations.append(self.migrator.rename_column(
|
||||
model._meta.table_name, old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def rename_table(self, model: peewee.Model, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames table in database.
|
||||
"""
|
||||
old_name = model._meta.table_name
|
||||
del self.table_dict[model._meta.table_name]
|
||||
model._meta.table_name = new_name
|
||||
self.table_dict[model._meta.table_name] = model
|
||||
self.operations.append(self.migrator.rename_table(old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_index(self, model: peewee.Model, *columns: str, unique=False) -> peewee.Model:
|
||||
"""Create indexes."""
|
||||
model._meta.indexes.append((columns, unique))
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = unique
|
||||
field.index = not unique
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
|
||||
columns_.append(col)
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, columns_, unique=unique))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_index(self, model: peewee.Model, *columns: str) -> peewee.Model:
|
||||
"""Drop indexes."""
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
if not field:
|
||||
continue
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = field.index = False
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
columns_.append(col)
|
||||
index_name = make_index_name(model._meta.table_name, columns_)
|
||||
model._meta.indexes = [(cols, _) for (
|
||||
cols, _) in model._meta.indexes if columns != cols]
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Add not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = False
|
||||
self.operations.append(self.migrator.add_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Drop not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = True
|
||||
self.operations.append(self.migrator.drop_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_default(self, model: peewee.Model, name: str, default: t.Any) -> peewee.Model:
|
||||
"""Add default."""
|
||||
field = model._meta.fields[name]
|
||||
model._meta.defaults[field] = field.default = default
|
||||
self.operations.append(self.migrator.apply_default(
|
||||
model._meta.table_name, name, field))
|
||||
return model
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
class MigrationManager(object):
|
||||
filemask = re.compile(r"[\d]+_[^\.]+\.py$")
|
||||
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migration manager.
|
||||
"""
|
||||
if not isinstance(database, (peewee.Database, peewee.Proxy)):
|
||||
raise RuntimeError('Invalid database: {}'.format(database))
|
||||
self.database = database
|
||||
|
||||
@cached_property
|
||||
def model(self) -> t.Type[MigrateHistory]:
|
||||
"""
|
||||
Initialize and cache the MigrationHistory model.
|
||||
"""
|
||||
MigrateHistory._meta.database = self.database
|
||||
MigrateHistory._meta.table_name = 'migratehistory'
|
||||
MigrateHistory._meta.schema = None
|
||||
MigrateHistory.create_table(True)
|
||||
return MigrateHistory
|
||||
|
||||
@property
|
||||
def done(self) -> t.List[str]:
|
||||
"""
|
||||
Scans migrations in the database.
|
||||
"""
|
||||
return [mm.name for mm in self.model.select().order_by(self.model.id)]
|
||||
|
||||
@property
|
||||
def todo(self):
|
||||
"""
|
||||
Scans migrations in the file system.
|
||||
"""
|
||||
if not os.path.exists(helper.migration_dir):
|
||||
logger.warning('Migration directory: {} does not exist.'.format(
|
||||
helper.migration_dir))
|
||||
os.makedirs(helper.migration_dir)
|
||||
return sorted(f[:-3] for f in os.listdir(helper.migration_dir) if self.filemask.match(f))
|
||||
|
||||
@property
|
||||
def diff(self) -> t.List[str]:
|
||||
"""
|
||||
Calculates difference between the filesystem and the database.
|
||||
"""
|
||||
done = set(self.done)
|
||||
return [name for name in self.todo if name not in done]
|
||||
|
||||
@cached_property
|
||||
def migrator(self) -> Migrator:
|
||||
"""
|
||||
Create migrator and setup it with fake migrations.
|
||||
"""
|
||||
migrator = Migrator(self.database)
|
||||
for name in self.done:
|
||||
self.up_one(name, migrator, True)
|
||||
return migrator
|
||||
|
||||
def compile(self, name, migrate='', rollback=''):
|
||||
"""
|
||||
Compiles a migration.
|
||||
"""
|
||||
name = datetime.utcnow().strftime('%Y%m%d%H%M%S') + '_' + name
|
||||
filename = name + '.py'
|
||||
path = os.path.join(helper.migration_dir, filename)
|
||||
with open(path, 'w') as f:
|
||||
f.write(MIGRATE_TEMPLATE.format(
|
||||
migrate=migrate, rollback=rollback, name=filename))
|
||||
|
||||
return name
|
||||
|
||||
def create(self, name: str = 'auto', auto: bool = False) -> t.Optional[str]:
|
||||
"""
|
||||
Creates a migration.
|
||||
"""
|
||||
migrate = rollback = ''
|
||||
if auto:
|
||||
raise NotImplementedError
|
||||
|
||||
logger.info('Creating migration "{}"'.format(name))
|
||||
name = self.compile(name, migrate, rollback)
|
||||
logger.info('Migration has been created as "{}"'.format(name))
|
||||
return name
|
||||
|
||||
def clear(self):
|
||||
"""Clear migrations."""
|
||||
self.model.delete().execute()
|
||||
|
||||
def up(self, name: t.Optional[str] = None):
|
||||
"""
|
||||
Runs all unapplied migrations.
|
||||
"""
|
||||
logger.info('Starting migrations')
|
||||
console.info('Starting migrations')
|
||||
|
||||
done = []
|
||||
diff = self.diff
|
||||
if not diff:
|
||||
logger.info('There is nothing to migrate')
|
||||
console.info('There is nothing to migrate')
|
||||
return done
|
||||
|
||||
migrator = self.migrator
|
||||
for mname in diff:
|
||||
done.append(self.up_one(mname, self.migrator))
|
||||
if name and name == mname:
|
||||
break
|
||||
|
||||
return done
|
||||
|
||||
def read(self, name: str):
|
||||
"""
|
||||
Reads a migration from a file.
|
||||
"""
|
||||
call_params = dict()
|
||||
if helper.is_os_windows() and sys.version_info >= (3, 0):
|
||||
# if system is windows - force utf-8 encoding
|
||||
call_params['encoding'] = 'utf-8'
|
||||
with open(os.path.join(helper.migration_dir, name + '.py'), **call_params) as f:
|
||||
code = f.read()
|
||||
scope = {}
|
||||
code = compile(code, '<string>', 'exec', dont_inherit=True)
|
||||
exec(code, scope, None)
|
||||
return scope.get('migrate', lambda m, d: None), scope.get('rollback', lambda m, d: None)
|
||||
|
||||
def up_one(self, name: str, migrator: Migrator,
|
||||
fake: bool = False, rollback: bool = False) -> str:
|
||||
"""
|
||||
Runs a migration with a given name.
|
||||
"""
|
||||
try:
|
||||
migrate_fn, rollback_fn = self.read(name)
|
||||
if fake:
|
||||
migrate_fn(migrator, self.database)
|
||||
migrator.clean()
|
||||
return name
|
||||
with self.database.transaction():
|
||||
if rollback:
|
||||
logger.info('Rolling back "{}"'.format(name))
|
||||
rollback_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
self.model.delete().where(self.model.name == name).execute()
|
||||
else:
|
||||
logger.info('Migrate "{}"'.format(name))
|
||||
migrate_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
if name not in self.done:
|
||||
self.model.create(name=name)
|
||||
|
||||
logger.info('Done "{}"'.format(name))
|
||||
return name
|
||||
|
||||
except Exception:
|
||||
self.database.rollback()
|
||||
operation_name = 'Rollback' if rollback else 'Migration'
|
||||
logger.exception('{} failed: {}'.format(operation_name, name))
|
||||
raise
|
||||
|
||||
def down(self):
|
||||
"""
|
||||
Rolls back migrations.
|
||||
"""
|
||||
if not self.done:
|
||||
raise RuntimeError('No migrations are found.')
|
||||
|
||||
name = self.done[-1]
|
||||
|
||||
migrator = self.migrator
|
||||
self.up_one(name, migrator, False, True)
|
||||
logger.warning('Rolled back migration: {}'.format(name))
|
||||
|
22
app/classes/shared/permission_helper.py
Normal file
22
app/classes/shared/permission_helper.py
Normal file
@ -0,0 +1,22 @@
|
||||
from enum import Enum
|
||||
|
||||
class PermissionHelper:
|
||||
@staticmethod
|
||||
def both_have_perm(a: str, b: str, permission_tested: Enum):
|
||||
return permission_helper.combine_perm_bool(a[permission_tested.value], b[permission_tested.value])
|
||||
|
||||
@staticmethod
|
||||
def combine_perm(a: str, b: str) -> str:
|
||||
return '1' if (a == '1' and b == '1') else '0'
|
||||
|
||||
@staticmethod
|
||||
def combine_perm_bool(a: str, b: str) -> bool:
|
||||
return a == '1' and b == '1'
|
||||
|
||||
@staticmethod
|
||||
def combine_masks(permission_mask_a: str, permission_mask_b: str) -> str:
|
||||
both_masks = zip(list(permission_mask_a), list(permission_mask_b))
|
||||
return ''.join(map(lambda x: permission_helper.combine_perm(x[0], x[1]), both_masks))
|
||||
|
||||
|
||||
permission_helper = PermissionHelper()
|
File diff suppressed because it is too large
Load Diff
@ -1,31 +1,29 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
import asyncio
|
||||
import shutil
|
||||
import datetime
|
||||
|
||||
from app.classes.controllers.users_controller import Users_Controller
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.models.management import management_helper
|
||||
from app.classes.models.users import users_helper
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.web.tornado import Webserver
|
||||
from app.classes.web.tornado_handler import Webserver
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.models.servers import servers_helper
|
||||
from app.classes.models.management import management_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import schedule
|
||||
from tzlocal import get_localzone
|
||||
from apscheduler.events import EVENT_JOB_EXECUTED
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
except ModuleNotFoundError as err:
|
||||
helper.auto_installer_fix(err)
|
||||
|
||||
logger = logging.getLogger('apscheduler')
|
||||
scheduler_intervals = { 'seconds',
|
||||
'minutes',
|
||||
'hours',
|
||||
@ -46,9 +44,13 @@ class TasksManager:
|
||||
self.controller = controller
|
||||
self.tornado = Webserver(controller, self)
|
||||
|
||||
self.tz = get_localzone()
|
||||
self.scheduler = BackgroundScheduler(timezone=str(self.tz))
|
||||
|
||||
self.users_controller = Users_Controller()
|
||||
|
||||
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread')
|
||||
|
||||
self.main_kill_switch_thread = threading.Thread(target=self.main_kill_switch, daemon=True, name="main_loop")
|
||||
self.main_thread_exiting = False
|
||||
|
||||
self.schedule_thread = threading.Thread(target=self.scheduler_thread, daemon=True, name="scheduler")
|
||||
@ -65,65 +67,49 @@ class TasksManager:
|
||||
def get_main_thread_run_status(self):
|
||||
return self.main_thread_exiting
|
||||
|
||||
def start_main_kill_switch_watcher(self):
|
||||
self.main_kill_switch_thread.start()
|
||||
|
||||
def main_kill_switch(self):
|
||||
while True:
|
||||
if os.path.exists(os.path.join(helper.root_dir, 'exit.txt')):
|
||||
logger.info("Found Exit File, stopping everything")
|
||||
self._main_graceful_exit()
|
||||
time.sleep(5)
|
||||
|
||||
def reload_schedule_from_db(self):
|
||||
jobs = management_helper.get_schedules_enabled()
|
||||
schedule.clear(tag='backup')
|
||||
schedule.clear(tag='db')
|
||||
for j in jobs:
|
||||
if j.interval_type in scheduler_intervals:
|
||||
logger.info("Loading schedule ID#{i}: '{a}' every {n} {t} at {s}".format(
|
||||
i=j.schedule_id, a=j.action, n=j.interval, t=j.interval_type, s=j.start_time))
|
||||
try:
|
||||
getattr(schedule.every(j.interval), j.interval_type).at(j.start_time).do(
|
||||
self.controller.management.send_command, 0, j.server_id, "127.27.23.89", j.action)
|
||||
except schedule.ScheduleValueError as e:
|
||||
logger.critical("Scheduler value error occurred: {} on ID#{}".format(e, j.schedule_id))
|
||||
else:
|
||||
logger.critical("Unknown schedule job type '{}' at id {}, skipping".format(j.interval_type, j.schedule_id))
|
||||
|
||||
logger.info("Reload from DB called. Current enabled schedules: ")
|
||||
for item in jobs:
|
||||
logger.info(f"JOB: {item}")
|
||||
|
||||
def command_watcher(self):
|
||||
while True:
|
||||
# select any commands waiting to be processed
|
||||
commands = management_helper.get_unactioned_commands()
|
||||
for c in commands:
|
||||
try:
|
||||
svr = self.controller.get_server_obj(c.server_id)
|
||||
except:
|
||||
logger.error("Server value requested does note exist purging item from waiting commands.")
|
||||
management_helper.mark_command_complete(c.command_id)
|
||||
|
||||
svr = self.controller.get_server_obj(c['server_id']['server_id'])
|
||||
user_lang = c.get('user')['lang']
|
||||
command = c.get('command', None)
|
||||
user_id = c.user_id
|
||||
command = c.command
|
||||
|
||||
if command == 'start_server':
|
||||
svr.run_threaded_server(user_lang)
|
||||
svr.run_threaded_server(user_id)
|
||||
|
||||
elif command == 'stop_server':
|
||||
svr.stop_threaded_server()
|
||||
|
||||
elif command == "restart_server":
|
||||
svr.restart_threaded_server(user_lang)
|
||||
svr.restart_threaded_server(user_id)
|
||||
|
||||
elif command == "backup_server":
|
||||
svr.backup_server()
|
||||
|
||||
elif command == "update_executable":
|
||||
svr.jar_update()
|
||||
management_helper.mark_command_complete(c.get('command_id', None))
|
||||
else:
|
||||
svr.send_command(command)
|
||||
management_helper.mark_command_complete(c.command_id)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def _main_graceful_exit(self):
|
||||
try:
|
||||
os.remove(helper.session_file)
|
||||
os.remove(os.path.join(helper.root_dir, 'exit.txt'))
|
||||
os.remove(os.path.join(helper.root_dir, '.header'))
|
||||
self.controller.stop_all_servers()
|
||||
except:
|
||||
logger.info("Caught error during shutdown", exc_info=True)
|
||||
@ -159,33 +145,283 @@ class TasksManager:
|
||||
console.info("Launching realtime thread...")
|
||||
self.realtime_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def scheduler_thread():
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
def scheduler_thread(self):
|
||||
schedules = management_helper.get_schedules_enabled()
|
||||
self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
|
||||
#self.scheduler.add_job(self.scheduler.print_jobs, 'interval', seconds=10, id='-1')
|
||||
|
||||
#load schedules from DB
|
||||
for schedule in schedules:
|
||||
if schedule.interval != 'reaction':
|
||||
if schedule.cron_string != "":
|
||||
try:
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
CronTrigger.from_crontab(schedule.cron_string,
|
||||
timezone=str(self.tz)),
|
||||
id = str(schedule.schedule_id),
|
||||
args = [schedule.server_id,
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
schedule.command]
|
||||
)
|
||||
except Exception as e:
|
||||
console.error(f"Failed to schedule task with error: {e}.")
|
||||
console.warning("Removing failed task from DB.")
|
||||
logger.error(f"Failed to schedule task with error: {e}.")
|
||||
logger.warning("Removing failed task from DB.")
|
||||
#remove items from DB if task fails to add to apscheduler
|
||||
management_helper.delete_scheduled_task(schedule.schedule_id)
|
||||
else:
|
||||
if schedule.interval_type == 'hours':
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
minute = 0,
|
||||
hour = '*/'+str(schedule.interval),
|
||||
id = str(schedule.schedule_id),
|
||||
args = [schedule.server_id,
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
schedule.command]
|
||||
)
|
||||
elif schedule.interval_type == 'minutes':
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
minute = '*/'+str(schedule.interval),
|
||||
id = str(schedule.schedule_id),
|
||||
args = [schedule.server_id,
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
schedule.command]
|
||||
)
|
||||
elif schedule.interval_type == 'days':
|
||||
curr_time = schedule.start_time.split(':')
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
day = '*/'+str(schedule.interval),
|
||||
hour=curr_time[0],
|
||||
minute=curr_time[1],
|
||||
id=str(schedule.schedule_id),
|
||||
args=[schedule.server_id,
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
schedule.command]
|
||||
)
|
||||
self.scheduler.start()
|
||||
jobs = self.scheduler.get_jobs()
|
||||
logger.info("Loaded schedules. Current enabled schedules: ")
|
||||
for item in jobs:
|
||||
logger.info(f"JOB: {item}")
|
||||
|
||||
def schedule_job(self, job_data):
|
||||
sch_id = management_helper.create_scheduled_task(
|
||||
job_data['server_id'],
|
||||
job_data['action'],
|
||||
job_data['interval'],
|
||||
job_data['interval_type'],
|
||||
job_data['start_time'],
|
||||
job_data['command'],
|
||||
"None",
|
||||
job_data['enabled'],
|
||||
job_data['one_time'],
|
||||
job_data['cron_string'],
|
||||
job_data['parent'],
|
||||
job_data['delay'])
|
||||
#Checks to make sure some doofus didn't actually make the newly created task a child of itself.
|
||||
if str(job_data['parent']) == str(sch_id):
|
||||
management_helper.update_scheduled_task(sch_id, {'parent':None})
|
||||
#Check to see if it's enabled and is not a chain reaction.
|
||||
if job_data['enabled'] and job_data['interval_type'] != 'reaction':
|
||||
if job_data['cron_string'] != "":
|
||||
try:
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
CronTrigger.from_crontab(job_data['cron_string'],
|
||||
timezone=str(self.tz)),
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']]
|
||||
)
|
||||
except Exception as e:
|
||||
console.error(f"Failed to schedule task with error: {e}.")
|
||||
console.warning("Removing failed task from DB.")
|
||||
logger.error(f"Failed to schedule task with error: {e}.")
|
||||
logger.warning("Removing failed task from DB.")
|
||||
#remove items from DB if task fails to add to apscheduler
|
||||
management_helper.delete_scheduled_task(sch_id)
|
||||
else:
|
||||
if job_data['interval_type'] == 'hours':
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
minute = 0,
|
||||
hour = '*/'+str(job_data['interval']),
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']]
|
||||
)
|
||||
elif job_data['interval_type'] == 'minutes':
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
minute = '*/'+str(job_data['interval']),
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']]
|
||||
)
|
||||
elif job_data['interval_type'] == 'days':
|
||||
curr_time = job_data['start_time'].split(':')
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
day = '*/'+str(job_data['interval']),
|
||||
hour = curr_time[0],
|
||||
minute = curr_time[1],
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']],
|
||||
)
|
||||
logger.info("Added job. Current enabled schedules: ")
|
||||
jobs = self.scheduler.get_jobs()
|
||||
for item in jobs:
|
||||
logger.info(f"JOB: {item}")
|
||||
|
||||
def remove_all_server_tasks(self, server_id):
|
||||
schedules = management_helper.get_schedules_by_server(server_id)
|
||||
for schedule in schedules:
|
||||
if schedule.interval != 'reaction':
|
||||
self.remove_job(schedule.schedule_id)
|
||||
|
||||
def remove_job(self, sch_id):
|
||||
job = management_helper.get_scheduled_task_model(sch_id)
|
||||
for schedule in management_helper.get_child_schedules(sch_id):
|
||||
management_helper.update_scheduled_task(schedule.schedule_id, {'parent':None})
|
||||
management_helper.delete_scheduled_task(sch_id)
|
||||
if job.enabled and job.interval_type != 'reaction':
|
||||
self.scheduler.remove_job(str(sch_id))
|
||||
logger.info(f"Job with ID {sch_id} was deleted.")
|
||||
else:
|
||||
logger.info(f"Job with ID {sch_id} was deleted from DB, but was not enabled."
|
||||
+ "Not going to try removing something that doesn't exist from active schedules.")
|
||||
|
||||
def update_job(self, sch_id, job_data):
|
||||
management_helper.update_scheduled_task(sch_id, job_data)
|
||||
#Checks to make sure some doofus didn't actually make the newly created task a child of itself.
|
||||
if str(job_data['parent']) == str(sch_id):
|
||||
management_helper.update_scheduled_task(sch_id, {'parent':None})
|
||||
try:
|
||||
if job_data['interval'] != 'reaction':
|
||||
self.scheduler.remove_job(str(sch_id))
|
||||
except:
|
||||
logger.info("No job found in update job. Assuming it was previously disabled. Starting new job.")
|
||||
|
||||
if job_data['enabled']:
|
||||
if job_data['interval'] != 'reaction':
|
||||
if job_data['cron_string'] != "":
|
||||
try:
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
CronTrigger.from_crontab(job_data['cron_string'],
|
||||
timezone=str(self.tz)),
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']]
|
||||
)
|
||||
except Exception as e:
|
||||
console.error(f"Failed to schedule task with error: {e}.")
|
||||
console.info("Removing failed task from DB.")
|
||||
management_helper.delete_scheduled_task(sch_id)
|
||||
else:
|
||||
if job_data['interval_type'] == 'hours':
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
minute = 0,
|
||||
hour = '*/'+str(job_data['interval']),
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']]
|
||||
)
|
||||
elif job_data['interval_type'] == 'minutes':
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
minute = '*/'+str(job_data['interval']),
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']]
|
||||
)
|
||||
elif job_data['interval_type'] == 'days':
|
||||
curr_time = job_data['start_time'].split(':')
|
||||
self.scheduler.add_job(management_helper.add_command,
|
||||
'cron',
|
||||
day = '*/'+str(job_data['interval']),
|
||||
hour = curr_time[0],
|
||||
minute = curr_time[1],
|
||||
id=str(sch_id),
|
||||
args=[job_data['server_id'],
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
job_data['command']]
|
||||
)
|
||||
else:
|
||||
try:
|
||||
self.scheduler.get_job(str(sch_id))
|
||||
self.scheduler.remove_job(str(sch_id))
|
||||
except:
|
||||
logger.info(f"APScheduler found no scheduled job on schedule update for schedule with id: {sch_id} Assuming it was already disabled.")
|
||||
|
||||
def schedule_watcher(self, event):
|
||||
if not event.exception:
|
||||
if str(event.job_id).isnumeric():
|
||||
task = management_helper.get_scheduled_task_model(int(event.job_id))
|
||||
management_helper.add_to_audit_log_raw('system', users_helper.get_user_id_by_name('system'), task.server_id,
|
||||
f"Task with id {task.schedule_id} completed successfully", '127.0.0.1')
|
||||
#check if the task is a single run.
|
||||
if task.one_time:
|
||||
self.remove_job(task.schedule_id)
|
||||
logger.info("one time task detected. Deleting...")
|
||||
#check for any child tasks for this. It's kind of backward, but this makes DB management a lot easier. One to one instead of one to many.
|
||||
for schedule in management_helper.get_child_schedules_by_server(task.schedule_id, task.server_id):
|
||||
#event job ID's are strings so we need to look at this as the same data type.
|
||||
if str(schedule.parent) == str(event.job_id):
|
||||
if schedule.enabled:
|
||||
delaytime = datetime.datetime.now() + datetime.timedelta(seconds=schedule.delay)
|
||||
self.scheduler.add_job(management_helper.add_command, 'date', run_date=delaytime, id=str(schedule.schedule_id),
|
||||
args=[schedule.server_id,
|
||||
self.users_controller.get_id_by_name('system'),
|
||||
'127.0.0.1',
|
||||
schedule.command])
|
||||
else:
|
||||
logger.info("Event job ID is not numerical. Assuming it's stats - not stored in DB. Moving on.")
|
||||
else:
|
||||
logger.error(f"Task failed with error: {event.exception}")
|
||||
|
||||
def start_stats_recording(self):
|
||||
stats_update_frequency = helper.get_setting('stats_update_frequency')
|
||||
logger.info("Stats collection frequency set to {stats} seconds".format(stats=stats_update_frequency))
|
||||
console.info("Stats collection frequency set to {stats} seconds".format(stats=stats_update_frequency))
|
||||
logger.info(f"Stats collection frequency set to {stats_update_frequency} seconds")
|
||||
console.info(f"Stats collection frequency set to {stats_update_frequency} seconds")
|
||||
|
||||
# one for now,
|
||||
self.controller.stats.record_stats()
|
||||
|
||||
# one for later
|
||||
schedule.every(stats_update_frequency).seconds.do(self.controller.stats.record_stats).tag('stats-recording')
|
||||
self.scheduler.add_job(self.controller.stats.record_stats, 'interval', seconds=stats_update_frequency, id="stats")
|
||||
|
||||
@staticmethod
|
||||
def serverjar_cache_refresher():
|
||||
|
||||
def serverjar_cache_refresher(self):
|
||||
logger.info("Refreshing serverjars.com cache on start")
|
||||
server_jar_obj.refresh_cache()
|
||||
|
||||
logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
|
||||
schedule.every(12).hours.do(server_jar_obj.refresh_cache).tag('serverjars')
|
||||
self.scheduler.add_job(server_jar_obj.refresh_cache, 'interval', hours=12, id="serverjars")
|
||||
|
||||
@staticmethod
|
||||
def realtime():
|
||||
def realtime(self):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
@ -210,9 +446,7 @@ class TasksManager:
|
||||
'mem_percent': host_stats.get('mem_percent'),
|
||||
'mem_usage': host_stats.get('mem_usage')
|
||||
})
|
||||
time.sleep(4)
|
||||
|
||||
def log_watcher(self):
|
||||
self.controller.servers.check_for_old_logs()
|
||||
schedule.every(6).hours.do(lambda: self.controller.servers.check_for_old_logs()).tag('log-mgmt')
|
||||
|
||||
self.scheduler.add_job(self.controller.servers.check_for_old_logs, 'interval', hours=6, id="log-mgmt")
|
||||
|
@ -1,71 +1,75 @@
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
import logging
|
||||
import os
|
||||
import typing as t
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Translation():
|
||||
class Translation:
|
||||
def __init__(self):
|
||||
self.translations_path = os.path.join(helper.root_dir, 'app', 'translations')
|
||||
self.cached_translation = None
|
||||
self.cached_translation_lang = None
|
||||
self.lang_file_exists = []
|
||||
def translate(self, page, word, lang):
|
||||
translated_word = None
|
||||
fallback_lang = 'en_EN'
|
||||
|
||||
if lang not in self.lang_file_exists and \
|
||||
helper.check_file_exists(os.path.join(self.translations_path, str(lang) + '.json')):
|
||||
self.lang_file_exists.append(lang)
|
||||
def get_language_file(self, language: str):
|
||||
return os.path.join(self.translations_path, str(language) + '.json')
|
||||
|
||||
translated_word = self.translate_inner(page, word, lang) \
|
||||
if lang in self.lang_file_exists else self.translate_inner(page, word, fallback_lang)
|
||||
def translate(self, page, word, language):
|
||||
fallback_language = 'en_EN'
|
||||
|
||||
translated_word = self.translate_inner(page, word, language)
|
||||
if translated_word is None:
|
||||
translated_word = self.translate_inner(page, word, fallback_language)
|
||||
|
||||
if translated_word:
|
||||
if isinstance(translated_word, dict): return json.dumps(translated_word)
|
||||
elif iter(translated_word) and not isinstance(translated_word, str): return '\n'.join(translated_word)
|
||||
return translated_word
|
||||
if isinstance(translated_word, dict):
|
||||
# JSON objects
|
||||
return json.dumps(translated_word)
|
||||
elif isinstance(translated_word, str):
|
||||
# Basic strings
|
||||
return translated_word
|
||||
elif hasattr(translated_word, '__iter__'):
|
||||
# Multiline strings
|
||||
return '\n'.join(translated_word)
|
||||
return 'Error while getting translation'
|
||||
def translate_inner(self, page, word, lang):
|
||||
lang_file = os.path.join(
|
||||
self.translations_path,
|
||||
lang + '.json'
|
||||
)
|
||||
|
||||
def translate_inner(self, page, word, language) -> t.Union[t.Any, None]:
|
||||
language_file = self.get_language_file(language)
|
||||
try:
|
||||
if not self.cached_translation:
|
||||
with open(lang_file, 'r', encoding='utf-8') as f:
|
||||
with open(language_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self.cached_translation = data
|
||||
elif self.cached_translation_lang != lang:
|
||||
with open(lang_file, 'r', encoding='utf-8') as f:
|
||||
elif self.cached_translation_lang != language:
|
||||
with open(language_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self.cached_translation = data
|
||||
self.cached_translation_lang = lang
|
||||
self.cached_translation_lang = language
|
||||
else:
|
||||
data = self.cached_translation
|
||||
|
||||
try:
|
||||
translated_page = data[page]
|
||||
except KeyError:
|
||||
logger.error('Translation File Error: page {} does not exist for lang {}'.format(page, lang))
|
||||
console.error('Translation File Error: page {} does not exist for lang {}'.format(page, lang))
|
||||
logger.error(f'Translation File Error: page {page} does not exist for lang {language}')
|
||||
console.error(f'Translation File Error: page {page} does not exist for lang {language}')
|
||||
return None
|
||||
|
||||
try:
|
||||
translated_word = translated_page[word]
|
||||
return translated_word
|
||||
except KeyError:
|
||||
logger.error('Translation File Error: word {} does not exist on page {} for lang {}'.format(word, page, lang))
|
||||
console.error('Translation File Error: word {} does not exist on page {} for lang {}'.format(word, page, lang))
|
||||
logger.error(f'Translation File Error: word {word} does not exist on page {page} for lang {language}')
|
||||
console.error(f'Translation File Error: word {word} does not exist on page {page} for lang {language}')
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.critical('Translation File Error: Unable to read {} due to {}'.format(lang_file, e))
|
||||
console.critical('Translation File Error: Unable to read {} due to {}'.format(lang_file, e))
|
||||
logger.critical(f'Translation File Error: Unable to read {language_file} due to {e}')
|
||||
console.critical(f'Translation File Error: Unable to read {language_file} due to {e}')
|
||||
return None
|
||||
|
||||
translation = Translation()
|
||||
|
||||
translation = Translation()
|
||||
|
@ -1,27 +1,27 @@
|
||||
import json
|
||||
import logging
|
||||
import tempfile
|
||||
import threading
|
||||
from typing import Container
|
||||
import zipfile
|
||||
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import bleach
|
||||
import os
|
||||
import shutil
|
||||
import html
|
||||
import re
|
||||
import logging
|
||||
import time
|
||||
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import Users, installer
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.translation import translation
|
||||
from app.classes.shared.server import ServerOutBuf
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
try:
|
||||
import bleach
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
|
||||
except ModuleNotFoundError as ex:
|
||||
helper.auto_installer_fix(ex)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AjaxHandler(BaseHandler):
|
||||
|
||||
def render_page(self, template, page_data):
|
||||
@ -33,13 +33,13 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
@tornado.web.authenticated
|
||||
def get(self, page):
|
||||
user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
_, _, exec_user = self.current_user
|
||||
error = bleach.clean(self.get_argument('error', "WTF Error!"))
|
||||
|
||||
template = "panel/denied.html"
|
||||
|
||||
page_data = {
|
||||
'user_data': user_data,
|
||||
'user_data': exec_user,
|
||||
'error': error
|
||||
}
|
||||
|
||||
@ -65,11 +65,11 @@ class AjaxHandler(BaseHandler):
|
||||
return
|
||||
|
||||
if not server_data['log_path']:
|
||||
logger.warning("Log path not found in server_log ajax call ({})".format(server_id))
|
||||
logger.warning(f"Log path not found in server_log ajax call ({server_id})")
|
||||
|
||||
if full_log:
|
||||
log_lines = helper.get_setting('max_log_lines')
|
||||
data = helper.tail_file(server_data['log_path'], log_lines)
|
||||
data = helper.tail_file(helper.get_os_understandable_path(server_data['log_path']), log_lines)
|
||||
else:
|
||||
data = ServerOutBuf.lines.get(server_id, [])
|
||||
|
||||
@ -79,70 +79,206 @@ class AjaxHandler(BaseHandler):
|
||||
d = re.sub('(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )', '', d)
|
||||
d = re.sub('[A-z]{2}\b\b', '', d)
|
||||
line = helper.log_colors(html.escape(d))
|
||||
self.write('{}<br />'.format(line))
|
||||
self.write(f'{line}<br />')
|
||||
# self.write(d.encode("utf-8"))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Skipping Log Line due to error: {}".format(e))
|
||||
pass
|
||||
logger.warning(f"Skipping Log Line due to error: {e}")
|
||||
|
||||
elif page == "announcements":
|
||||
data = helper.get_announcements()
|
||||
page_data['notify_data'] = data
|
||||
self.render_page('ajax/notify.html', page_data)
|
||||
|
||||
elif page == "get_file":
|
||||
file_path = self.get_argument('file_path', None)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
elif page == "get_zip_tree":
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in get_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in get_file ajax call ({})".format(file_path))
|
||||
return
|
||||
|
||||
|
||||
error = None
|
||||
|
||||
try:
|
||||
with open(file_path) as file:
|
||||
file_contents = file.read()
|
||||
except UnicodeDecodeError:
|
||||
file_contents = ''
|
||||
error = 'UnicodeDecodeError'
|
||||
|
||||
self.write({
|
||||
'content': file_contents,
|
||||
'error': error
|
||||
})
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_zip_tree(path))
|
||||
self.finish()
|
||||
|
||||
elif page == "get_tree":
|
||||
elif page == "get_zip_dir":
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_zip_dir(path))
|
||||
self.finish()
|
||||
|
||||
elif page == "get_backup_tree":
|
||||
server_id = self.get_argument('id', None)
|
||||
folder = self.get_argument('path', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_tree'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
output = ""
|
||||
|
||||
self.write(self.controller.servers.get_server_data_by_id(server_id)['path'] + '\n' +
|
||||
helper.generate_tree(self.controller.servers.get_server_data_by_id(server_id)['path']))
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold)
|
||||
output += \
|
||||
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
|
||||
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if str(dpath) in self.controller.management.get_excluded_backup_dirs(server_id):
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="checkbox" class="checkBoxClass" name="root_path" value="{dpath}" checked>
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>
|
||||
\n"""\
|
||||
|
||||
else:
|
||||
output += f"""<li
|
||||
class="tree-nested d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}" checked><span style="margin-right: 6px;">
|
||||
<i class="far fa-file"></i></span></input>{filename}</li>"""
|
||||
|
||||
else:
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="checkbox" class="checkBoxClass" name="root_path" value="{dpath}">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>
|
||||
\n"""\
|
||||
|
||||
else:
|
||||
output += f"""<li
|
||||
class="tree-nested d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}">
|
||||
<span style="margin-right: 6px;"><i class="far fa-file"></i></span></input>{filename}</li>"""
|
||||
self.write(helper.get_os_understandable_path(folder) + '\n' +
|
||||
output)
|
||||
self.finish()
|
||||
|
||||
elif page == "get_backup_dir":
|
||||
server_id = self.get_argument('id', None)
|
||||
folder = self.get_argument('path', None)
|
||||
output = ""
|
||||
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold)
|
||||
output += \
|
||||
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
|
||||
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if str(dpath) in self.controller.management.get_excluded_backup_dirs(server_id):
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="checkbox" name="root_path" value="{dpath}">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>"""\
|
||||
|
||||
else:
|
||||
output += f"""<li
|
||||
class="tree-item tree-nested d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' name='root_path' value='{dpath}'><span style="margin-right: 6px;">
|
||||
<i class="far fa-file"></i></span></input>{filename}</li>"""
|
||||
|
||||
else:
|
||||
if os.path.isdir(rel):
|
||||
output += \
|
||||
f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="checkbox" name="root_path" value="{dpath}">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>"""\
|
||||
|
||||
else:
|
||||
output += f"""<li
|
||||
class="tree-item tree-nested d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' name='root_path' value='{dpath}'>
|
||||
<span style="margin-right: 6px;"><i class="far fa-file"></i></span></input>{filename}</li>"""
|
||||
|
||||
self.write(helper.get_os_understandable_path(folder) + '\n' +
|
||||
output)
|
||||
self.finish()
|
||||
|
||||
elif page == "get_dir":
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_tree'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_dir(path))
|
||||
self.finish()
|
||||
|
||||
@tornado.web.authenticated
|
||||
def post(self, page):
|
||||
user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
error = bleach.clean(self.get_argument('error', "WTF Error!"))
|
||||
api_key, _, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
page_data = {
|
||||
'user_data': user_data,
|
||||
'error': error
|
||||
}
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
|
||||
|
||||
if page == "send_command":
|
||||
command = self.get_body_argument('command', default=None, strip=True)
|
||||
server_id = self.get_argument('id')
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if server_id is None:
|
||||
logger.warning("Server ID not found in send_command ajax call")
|
||||
@ -150,182 +286,213 @@ class AjaxHandler(BaseHandler):
|
||||
|
||||
srv_obj = self.controller.get_server_obj(server_id)
|
||||
|
||||
if command == srv_obj.settings['stop_command']:
|
||||
logger.info("Stop command detected as terminal input - intercepting." +
|
||||
f"Starting Crafty's stop process for server with id: {server_id}")
|
||||
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), 'stop_server')
|
||||
command = None
|
||||
elif command == 'restart':
|
||||
logger.info("Restart command detected as terminal input - intercepting." +
|
||||
f"Starting Crafty's stop process for server with id: {server_id}")
|
||||
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), 'restart_server')
|
||||
command = None
|
||||
if command:
|
||||
if srv_obj.check_running():
|
||||
srv_obj.send_command(command)
|
||||
|
||||
self.controller.management.add_to_audit_log(user_data['user_id'], "Sent command to {} terminal: {}".format(self.controller.servers.get_server_friendly_name(server_id), command), server_id, self.get_remote_ip())
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"Sent command to {self.controller.servers.get_server_friendly_name(server_id)} terminal: {command}",
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
|
||||
elif page == "create_file":
|
||||
file_parent = self.get_body_argument('file_parent', default=None, strip=True)
|
||||
file_name = self.get_body_argument('file_name', default=None, strip=True)
|
||||
file_path = os.path.join(file_parent, file_name)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'create_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path) \
|
||||
or helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in create_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in create_file ajax call ({})".format(file_path))
|
||||
return
|
||||
|
||||
# Create the file by opening it
|
||||
with open(file_path, 'w') as file_object:
|
||||
file_object.close()
|
||||
|
||||
elif page == "create_dir":
|
||||
dir_parent = self.get_body_argument('dir_parent', default=None, strip=True)
|
||||
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
|
||||
dir_path = os.path.join(dir_parent, dir_name)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'create_dir'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], dir_path) \
|
||||
or helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
|
||||
console.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
|
||||
return
|
||||
# Create the directory
|
||||
os.mkdir(dir_path)
|
||||
|
||||
elif page == "unzip_file":
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
helper.unzipFile(path)
|
||||
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
|
||||
elif page == "send_order":
|
||||
self.controller.users.update_server_order(exec_user['user_id'], bleach.clean(self.get_argument('order')))
|
||||
return
|
||||
|
||||
elif page == "clear_comms":
|
||||
if exec_user['superuser']:
|
||||
self.controller.clear_unexecuted_commands()
|
||||
return
|
||||
|
||||
elif page == "kill":
|
||||
if not permissions['Commands'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Commands")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
svr = self.controller.get_server_obj(server_id)
|
||||
try:
|
||||
svr.kill()
|
||||
except Exception as e:
|
||||
logger.error("Could not find PID for requested termsig. Full error: {}".format(e))
|
||||
logger.error(f"Could not find PID for requested termsig. Full error: {e}")
|
||||
return
|
||||
elif page == "eula":
|
||||
server_id = self.get_argument('id', None)
|
||||
svr = self.controller.get_server_obj(server_id)
|
||||
svr.agree_eula(exec_user['user_id'])
|
||||
|
||||
elif page == "restore_backup":
|
||||
if not permissions['Backup'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Backups")
|
||||
return
|
||||
server_id = bleach.clean(self.get_argument('id', None))
|
||||
zip_name = bleach.clean(self.get_argument('zip_file', None))
|
||||
svr_obj = self.controller.servers.get_server_obj(server_id)
|
||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if server_data['type'] == 'minecraft-java':
|
||||
backup_path = svr_obj.backup_path
|
||||
if helper.validate_traversal(backup_path, zip_name):
|
||||
tempDir = helper.unzip_backup_archive(backup_path, zip_name)
|
||||
new_server = self.controller.import_zip_server(svr_obj.server_name,
|
||||
tempDir,
|
||||
server_data['executable'],
|
||||
'1', '2',
|
||||
server_data['server_port'])
|
||||
new_server_id = new_server
|
||||
new_server = self.controller.get_server_data(new_server)
|
||||
self.controller.rename_backup_dir(server_id, new_server_id, new_server['server_uuid'])
|
||||
self.controller.remove_server(server_id, True)
|
||||
self.redirect('/panel/dashboard')
|
||||
|
||||
else:
|
||||
backup_path = svr_obj.backup_path
|
||||
if helper.validate_traversal(backup_path, zip_name):
|
||||
tempDir = helper.unzip_backup_archive(backup_path, zip_name)
|
||||
new_server = self.controller.import_bedrock_zip_server(svr_obj.server_name,
|
||||
tempDir,
|
||||
server_data['executable'],
|
||||
server_data['server_port'])
|
||||
new_server_id = new_server
|
||||
new_server = self.controller.get_server_data(new_server)
|
||||
self.controller.rename_backup_dir(server_id, new_server_id, new_server['server_uuid'])
|
||||
self.controller.remove_server(server_id, True)
|
||||
self.redirect('/panel/dashboard')
|
||||
|
||||
elif page == "unzip_server":
|
||||
path = self.get_argument('path', None)
|
||||
if helper.check_file_exists(path):
|
||||
helper.unzipServer(path, exec_user['user_id'])
|
||||
else:
|
||||
user_id = exec_user['user_id']
|
||||
if user_id:
|
||||
time.sleep(5)
|
||||
user_lang = self.controller.users.get_user_lang_by_id(user_id)
|
||||
websocket_helper.broadcast_user(user_id, 'send_start_error',{
|
||||
'error': translation.translate('error', 'no-file', user_lang)
|
||||
})
|
||||
return
|
||||
|
||||
elif page == "backup_select":
|
||||
path = self.get_argument('path', None)
|
||||
helper.backup_select(path, exec_user['user_id'])
|
||||
return
|
||||
|
||||
|
||||
@tornado.web.authenticated
|
||||
def delete(self, page):
|
||||
if page == "del_file":
|
||||
file_path = self.get_body_argument('file_path', default=None, strip=True)
|
||||
api_key, _, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
|
||||
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
|
||||
if page == "del_task":
|
||||
if not permissions['Schedule'] in user_perms:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Tasks")
|
||||
else:
|
||||
sch_id = self.get_argument('schedule_id', '-404')
|
||||
self.tasks_manager.remove_job(sch_id)
|
||||
|
||||
if page == "del_backup":
|
||||
if not permissions['Backup'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Backups")
|
||||
return
|
||||
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if os.name == "nt":
|
||||
file_path = file_path.replace('/', "\\")
|
||||
console.warning(f"Delete {file_path} for server {server_id}")
|
||||
|
||||
console.warning("delete {} for server {}".format(file_path, server_id))
|
||||
|
||||
if not self.check_server_id(server_id, 'del_file'):
|
||||
if not self.check_server_id(server_id, 'del_backup'):
|
||||
return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not (helper.in_path(server_info['path'], file_path) \
|
||||
or helper.in_path(server_info['backup_path'], file_path)) \
|
||||
if not (helper.in_path(helper.get_os_understandable_path(server_info['path']), file_path) \
|
||||
or helper.in_path(helper.get_os_understandable_path(server_info['backup_path']), file_path)) \
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
logger.warning(f"Invalid path in del_backup ajax call ({file_path})")
|
||||
console.warning(f"Invalid path in del_backup ajax call ({file_path})")
|
||||
return
|
||||
|
||||
# Delete the file
|
||||
os.remove(file_path)
|
||||
|
||||
elif page == "del_dir":
|
||||
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
console.warning("delete {} for server {}".format(dir_path, server_id))
|
||||
|
||||
if not self.check_server_id(server_id, 'del_dir'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not helper.in_path(server_info['path'], dir_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning("Invalid path in del_file ajax call ({})".format(dir_path))
|
||||
console.warning("Invalid path in del_file ajax call ({})".format(dir_path))
|
||||
return
|
||||
|
||||
# Delete the directory
|
||||
# os.rmdir(dir_path) # Would only remove empty directories
|
||||
shutil.rmtree(dir_path) # Removes also when there are contents
|
||||
if helper.validate_traversal(helper.get_os_understandable_path(server_info['backup_path']), file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
elif page == "delete_server":
|
||||
if not permissions['Config'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
logger.info(
|
||||
"Removing server from panel for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
|
||||
logger.info(f"Removing server from panel for server: {self.controller.servers.get_server_friendly_name(server_id)}")
|
||||
|
||||
server_data = self.controller.get_server_data(server_id)
|
||||
server_name = server_data['server_name']
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, False)
|
||||
|
||||
elif page == "delete_server_files":
|
||||
if not permissions['Config'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Config")
|
||||
return
|
||||
server_id = self.get_argument('id', None)
|
||||
logger.info(
|
||||
"Removing server and all associated files for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
|
||||
logger.info(f"Removing server and all associated files for server: {self.controller.servers.get_server_friendly_name(server_id)}")
|
||||
|
||||
server_data = self.controller.get_server_data(server_id)
|
||||
server_name = server_data['server_name']
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"Deleted server {server_id} named {server_name}",
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
|
||||
self.tasks_manager.remove_all_server_tasks(server_id)
|
||||
self.controller.remove_server(server_id, True)
|
||||
|
||||
@tornado.web.authenticated
|
||||
def put(self, page):
|
||||
if page == "save_file":
|
||||
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
|
||||
file_path = self.get_body_argument('file_path', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'save_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in save_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in save_file ajax call ({})".format(file_path))
|
||||
return
|
||||
|
||||
# Open the file in write mode and store the content in file_object
|
||||
with open(file_path, 'w') as file_object:
|
||||
file_object.write(file_contents)
|
||||
|
||||
elif page == "rename_item":
|
||||
item_path = self.get_body_argument('item_path', default=None, strip=True)
|
||||
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'rename_item'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if item_path is None or new_item_name is None:
|
||||
logger.warning("Invalid path(s) in rename_item ajax call")
|
||||
console.warning("Invalid path(s) in rename_item ajax call")
|
||||
return
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], item_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(item_path)):
|
||||
logger.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
|
||||
console.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
|
||||
return
|
||||
|
||||
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], new_item_path) \
|
||||
or helper.check_path_exists(os.path.abspath(new_item_path)):
|
||||
logger.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
|
||||
console.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
|
||||
return
|
||||
|
||||
# RENAME
|
||||
os.rename(item_path, new_item_path)
|
||||
def check_server_id(self, server_id, page_name):
|
||||
if server_id is None:
|
||||
logger.warning("Server ID not defined in {} ajax call ({})".format(page_name, server_id))
|
||||
console.warning("Server ID not defined in {} ajax call ({})".format(page_name, server_id))
|
||||
logger.warning(f"Server ID not defined in {page_name} ajax call ({server_id})")
|
||||
console.warning(f"Server ID not defined in {page_name} ajax call ({server_id})")
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
# does this server id exist?
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
logger.warning("Server ID not found in {} ajax call ({})".format(page_name, server_id))
|
||||
console.warning("Server ID not found in {} ajax call ({})".format(page_name, server_id))
|
||||
logger.warning(f"Server ID not found in {page_name} ajax call ({server_id})")
|
||||
console.warning(f"Server ID not found in {page_name} ajax call ({server_id})")
|
||||
return
|
||||
return True
|
||||
|
@ -1,57 +1,65 @@
|
||||
import os
|
||||
import secrets
|
||||
import threading
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import logging
|
||||
import re
|
||||
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
bearer_pattern = re.compile(r'^Bearer', flags=re.IGNORECASE)
|
||||
|
||||
class ApiHandler(BaseHandler):
|
||||
|
||||
|
||||
def return_response(self, status: int, data: dict):
|
||||
# Define a standardized response
|
||||
# Define a standardized response
|
||||
self.set_status(status)
|
||||
self.write(data)
|
||||
|
||||
|
||||
def access_denied(self, user, reason=''):
|
||||
if reason: reason = ' because ' + reason
|
||||
log.info("User %s from IP %s was denied access to the API route " + self.request.path + reason, user, self.get_remote_ip())
|
||||
if reason:
|
||||
reason = ' because ' + reason
|
||||
logger.info("User %s from IP %s was denied access to the API route " + self.request.path + reason, user, self.get_remote_ip())
|
||||
self.finish(self.return_response(403, {
|
||||
'error':'ACCESS_DENIED',
|
||||
'info':'You were denied access to the requested resource'
|
||||
}))
|
||||
|
||||
|
||||
def authenticate_user(self) -> bool:
|
||||
try:
|
||||
log.debug("Searching for specified token")
|
||||
# TODO: YEET THIS
|
||||
user_data = self.controller.users.get_user_by_api_token(self.get_argument('token'))
|
||||
log.debug("Checking results")
|
||||
logger.debug("Searching for specified token")
|
||||
|
||||
api_token = self.get_argument('token', '')
|
||||
if api_token is None and self.request.headers.get('Authorization'):
|
||||
api_token = bearer_pattern.sub('', self.request.headers.get('Authorization'))
|
||||
elif api_token is None:
|
||||
api_token = self.get_cookie('token')
|
||||
user_data = self.controller.users.get_user_by_api_token(api_token)
|
||||
|
||||
logger.debug("Checking results")
|
||||
if user_data:
|
||||
# Login successful! Check perms
|
||||
log.info("User {} has authenticated to API".format(user_data['username']))
|
||||
# TODO: Role check
|
||||
logger.info(f"User {user_data['username']} has authenticated to API")
|
||||
# TODO: Role check
|
||||
|
||||
return True # This is to set the "authenticated"
|
||||
else:
|
||||
logging.debug("Auth unsuccessful")
|
||||
self.access_denied("unknown", "the user provided an invalid token")
|
||||
return
|
||||
return False
|
||||
except Exception as e:
|
||||
log.warning("An error occured while authenticating an API user: %s", e)
|
||||
self.access_denied("unknown"), "an error occured while authenticating the user"
|
||||
return
|
||||
logger.warning("An error occured while authenticating an API user: %s", e)
|
||||
self.finish(self.return_response(403, {
|
||||
'error':'ACCESS_DENIED',
|
||||
'info':'An error occured while authenticating the user'
|
||||
}))
|
||||
return False
|
||||
|
||||
|
||||
class ServersStats(ApiHandler):
|
||||
def get(self):
|
||||
"""Get details about all servers"""
|
||||
authenticated = self.authenticate_user()
|
||||
if not authenticated: return
|
||||
if not authenticated:
|
||||
return
|
||||
|
||||
# Get server stats
|
||||
# TODO Check perms
|
||||
self.finish(self.write({"servers": self.controller.stats.get_servers_stats()}))
|
||||
@ -61,7 +69,9 @@ class NodeStats(ApiHandler):
|
||||
def get(self):
|
||||
"""Get stats for particular node"""
|
||||
authenticated = self.authenticate_user()
|
||||
if not authenticated: return
|
||||
if not authenticated:
|
||||
return
|
||||
|
||||
# Get node stats
|
||||
node_stats = self.controller.stats.get_node_stats()
|
||||
node_stats.pop("servers")
|
||||
|
@ -1,23 +1,31 @@
|
||||
import logging
|
||||
import tornado.web
|
||||
import bleach
|
||||
from typing import (
|
||||
Union,
|
||||
List,
|
||||
Optional
|
||||
Optional, Tuple, Dict, Any
|
||||
)
|
||||
|
||||
from app.classes.models.users import ApiKeys
|
||||
from app.classes.shared.authentication import authentication
|
||||
from app.classes.shared.main_controller import Controller
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
try:
|
||||
import tornado.web
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseHandler(tornado.web.RequestHandler):
|
||||
|
||||
nobleach = {bool, type(None)}
|
||||
redactables = ("pass", "api")
|
||||
|
||||
def initialize(self, controller : Controller = None, tasks_manager=None, translator=None):
|
||||
# noinspection PyAttributeOutsideInit
|
||||
def initialize(self, controller: Controller = None, tasks_manager=None, translator=None):
|
||||
self.controller = controller
|
||||
self.tasks_manager = tasks_manager
|
||||
self.translator = translator
|
||||
@ -28,16 +36,17 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
self.request.remote_ip
|
||||
return remote_ip
|
||||
|
||||
def get_current_user(self):
|
||||
return self.get_secure_cookie("user", max_age_days=1)
|
||||
current_user: Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]
|
||||
def get_current_user(self) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
|
||||
return authentication.check(self.get_cookie("token"))
|
||||
|
||||
def autobleach(self, name, text):
|
||||
for r in self.redactables:
|
||||
if r in name:
|
||||
logger.debug("Auto-bleaching {}: {}".format(name, "[**REDACTED**]"))
|
||||
logger.debug(f"Auto-bleaching {name}: [**REDACTED**]")
|
||||
break
|
||||
else:
|
||||
logger.debug("Auto-bleaching {}: {}".format(name, text))
|
||||
logger.debug(f"Auto-bleaching {name}: {text}")
|
||||
if type(text) in self.nobleach:
|
||||
logger.debug("Auto-bleaching - bypass type")
|
||||
return text
|
||||
@ -54,7 +63,8 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
return self.autobleach(name, arg)
|
||||
|
||||
def get_arguments(self, name: str, strip: bool = True) -> List[str]:
|
||||
assert isinstance(strip, bool)
|
||||
if not isinstance(strip, bool):
|
||||
raise AssertionError
|
||||
args = self._get_arguments(name, self.request.arguments, strip)
|
||||
args_ret = []
|
||||
for arg in args:
|
||||
|
@ -4,10 +4,10 @@ from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DefaultHandler(BaseHandler):
|
||||
|
||||
# Override prepare() instead of get() to cover all possible HTTP methods.
|
||||
# pylint: disable=arguments-differ
|
||||
def prepare(self, page=None):
|
||||
if page is not None:
|
||||
self.set_status(404)
|
||||
@ -20,4 +20,3 @@ class DefaultHandler(BaseHandler):
|
||||
"/public/login",
|
||||
#translate=self.translator.translate,
|
||||
)
|
||||
|
||||
|
414
app/classes/web/file_handler.py
Normal file
414
app/classes/web/file_handler.py
Normal file
@ -0,0 +1,414 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.file_helpers import file_helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
try:
|
||||
import bleach
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FileHandler(BaseHandler):
|
||||
|
||||
def render_page(self, template, page_data):
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
)
|
||||
|
||||
@tornado.web.authenticated
|
||||
def get(self, page):
|
||||
api_key, _, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
|
||||
|
||||
if page == "get_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
file_path = helper.get_os_understandable_path(self.get_argument('file_path', None))
|
||||
|
||||
if not self.check_server_id(server_id, 'get_file'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning(f"Invalid path in get_file file file ajax call ({file_path})")
|
||||
console.warning(f"Invalid path in get_file file file ajax call ({file_path})")
|
||||
return
|
||||
|
||||
|
||||
error = None
|
||||
|
||||
try:
|
||||
with open(file_path, encoding='utf-8') as file:
|
||||
file_contents = file.read()
|
||||
except UnicodeDecodeError:
|
||||
file_contents = ''
|
||||
error = 'UnicodeDecodeError'
|
||||
|
||||
self.write({
|
||||
'content': file_contents,
|
||||
'error': error
|
||||
})
|
||||
self.finish()
|
||||
|
||||
elif page == "get_tree":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_tree'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_tree(path))
|
||||
self.finish()
|
||||
|
||||
elif page == "get_dir":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
path = self.get_argument('path', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_tree'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
|
||||
self.write(helper.get_os_understandable_path(path) + '\n' +
|
||||
helper.generate_dir(path))
|
||||
self.finish()
|
||||
|
||||
@tornado.web.authenticated
|
||||
def post(self, page):
|
||||
api_key, _, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
|
||||
|
||||
if page == "create_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
file_parent = helper.get_os_understandable_path(self.get_body_argument('file_parent', default=None, strip=True))
|
||||
file_name = self.get_body_argument('file_name', default=None, strip=True)
|
||||
file_path = os.path.join(file_parent, file_name)
|
||||
|
||||
if not self.check_server_id(server_id, 'create_file'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), file_path) \
|
||||
or helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning(f"Invalid path in create_file file ajax call ({file_path})")
|
||||
console.warning(f"Invalid path in create_file file ajax call ({file_path})")
|
||||
return
|
||||
|
||||
# Create the file by opening it
|
||||
with open(file_path, 'w', encoding='utf-8') as file_object:
|
||||
file_object.close()
|
||||
|
||||
elif page == "create_dir":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
dir_parent = helper.get_os_understandable_path(self.get_body_argument('dir_parent', default=None, strip=True))
|
||||
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
|
||||
dir_path = os.path.join(dir_parent, dir_name)
|
||||
|
||||
if not self.check_server_id(server_id, 'create_dir'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), dir_path) \
|
||||
or helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning(f"Invalid path in create_dir file ajax call ({dir_path})")
|
||||
console.warning(f"Invalid path in create_dir file ajax call ({dir_path})")
|
||||
return
|
||||
# Create the directory
|
||||
os.mkdir(dir_path)
|
||||
|
||||
elif page == "unzip_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
path = helper.get_os_understandable_path(self.get_argument('path', None))
|
||||
helper.unzipFile(path)
|
||||
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
|
||||
return
|
||||
|
||||
|
||||
@tornado.web.authenticated
|
||||
def delete(self, page):
|
||||
api_key, _, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
|
||||
if page == "del_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
|
||||
|
||||
console.warning(f"Delete {file_path} for server {server_id}")
|
||||
|
||||
if not self.check_server_id(server_id, 'del_file'):
|
||||
return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not (helper.in_path(helper.get_os_understandable_path(server_info['path']), file_path) \
|
||||
or helper.in_path(helper.get_os_understandable_path(server_info['backup_path']), file_path)) \
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning(f"Invalid path in del_file file ajax call ({file_path})")
|
||||
console.warning(f"Invalid path in del_file file ajax call ({file_path})")
|
||||
return
|
||||
|
||||
# Delete the file
|
||||
file_helper.del_file(file_path)
|
||||
|
||||
elif page == "del_dir":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
dir_path = helper.get_os_understandable_path(self.get_body_argument('dir_path', default=None, strip=True))
|
||||
|
||||
console.warning(f"Delete {dir_path} for server {server_id}")
|
||||
|
||||
if not self.check_server_id(server_id, 'del_dir'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not helper.in_path(helper.get_os_understandable_path(server_info['path']), dir_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning(f"Invalid path in del_file file ajax call ({dir_path})")
|
||||
console.warning(f"Invalid path in del_file file ajax call ({dir_path})")
|
||||
return
|
||||
|
||||
# Delete the directory
|
||||
# os.rmdir(dir_path) # Would only remove empty directories
|
||||
if helper.validate_traversal(helper.get_os_understandable_path(server_info['path']), dir_path):
|
||||
# Removes also when there are contents
|
||||
file_helper.del_dirs(dir_path)
|
||||
|
||||
@tornado.web.authenticated
|
||||
def put(self, page):
|
||||
api_key, _, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
server_id = self.get_argument('id', None)
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
|
||||
if page == "save_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
|
||||
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
|
||||
|
||||
if not self.check_server_id(server_id, 'save_file'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning(f"Invalid path in save_file file ajax call ({file_path})")
|
||||
console.warning(f"Invalid path in save_file file ajax call ({file_path})")
|
||||
return
|
||||
|
||||
# Open the file in write mode and store the content in file_object
|
||||
with open(file_path, 'w', encoding='utf-8') as file_object:
|
||||
file_object.write(file_contents)
|
||||
|
||||
elif page == "rename_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
item_path = helper.get_os_understandable_path(self.get_body_argument('item_path', default=None, strip=True))
|
||||
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
|
||||
|
||||
if not self.check_server_id(server_id, 'rename_file'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if item_path is None or new_item_name is None:
|
||||
logger.warning("Invalid path(s) in rename_file file ajax call")
|
||||
console.warning("Invalid path(s) in rename_file file ajax call")
|
||||
return
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), item_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(item_path)):
|
||||
logger.warning(f"Invalid old name path in rename_file file ajax call ({server_id})")
|
||||
console.warning(f"Invalid old name path in rename_file file ajax call ({server_id})")
|
||||
return
|
||||
|
||||
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']),
|
||||
new_item_path) \
|
||||
or helper.check_path_exists(os.path.abspath(new_item_path)):
|
||||
logger.warning(f"Invalid new name path in rename_file file ajax call ({server_id})")
|
||||
console.warning(f"Invalid new name path in rename_file file ajax call ({server_id})")
|
||||
return
|
||||
|
||||
# RENAME
|
||||
os.rename(item_path, new_item_path)
|
||||
|
||||
|
||||
@tornado.web.authenticated
|
||||
def patch(self, page):
|
||||
api_key, _, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
server_id = self.get_argument('id', None)
|
||||
permissions = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
|
||||
if page == "rename_file":
|
||||
if not permissions['Files'] in user_perms:
|
||||
if not superuser:
|
||||
self.redirect("/panel/error?error=Unauthorized access to Files")
|
||||
return
|
||||
item_path = helper.get_os_understandable_path(self.get_body_argument('item_path', default=None, strip=True))
|
||||
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
|
||||
|
||||
if not self.check_server_id(server_id, 'rename_file'):
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
if item_path is None or new_item_name is None:
|
||||
logger.warning("Invalid path(s) in rename_file file ajax call")
|
||||
console.warning("Invalid path(s) in rename_file file ajax call")
|
||||
return
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), item_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(item_path)):
|
||||
logger.warning(f"Invalid old name path in rename_file file ajax call ({server_id})")
|
||||
console.warning(f"Invalid old name path in rename_file file ajax call ({server_id})")
|
||||
return
|
||||
|
||||
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
|
||||
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']),
|
||||
new_item_path) \
|
||||
or helper.check_path_exists(os.path.abspath(new_item_path)):
|
||||
logger.warning(f"Invalid new name path in rename_file file ajax call ({server_id})")
|
||||
console.warning(f"Invalid new name path in rename_file file ajax call ({server_id})")
|
||||
return
|
||||
|
||||
# RENAME
|
||||
os.rename(item_path, new_item_path)
|
||||
|
||||
def check_server_id(self, server_id, page_name):
|
||||
if server_id is None:
|
||||
logger.warning(f"Server ID not defined in {page_name} file ajax call ({server_id})")
|
||||
console.warning(f"Server ID not defined in {page_name} file ajax call ({server_id})")
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
# does this server id exist?
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
logger.warning(f"Server ID not found in {page_name} file ajax call ({server_id})")
|
||||
console.warning(f"Server ID not found in {page_name} file ajax call ({server_id})")
|
||||
return
|
||||
return True
|
@ -1,25 +1,15 @@
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import requests
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import Users, fn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import bleach
|
||||
import requests
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class HTTPHandler(BaseHandler):
|
||||
def get(self):
|
||||
@ -34,13 +24,13 @@ class HTTPHandler(BaseHandler):
|
||||
try:
|
||||
resp = requests.get(url + ":" + str(port))
|
||||
resp.raise_for_status()
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
port = db_port
|
||||
self.redirect(url+":"+str(port))
|
||||
|
||||
|
||||
class HTTPHandlerPage(BaseHandler):
|
||||
def get(self, page):
|
||||
def get(self):
|
||||
url = str(self.request.host)
|
||||
port = 443
|
||||
url_list = url.split(":")
|
||||
@ -52,6 +42,6 @@ class HTTPHandlerPage(BaseHandler):
|
||||
try:
|
||||
resp = requests.get(url + ":" + str(port))
|
||||
resp.raise_for_status()
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
port = db_port
|
||||
self.redirect(url+":"+str(port))
|
||||
self.redirect(url+":"+str(port))
|
||||
|
@ -1,49 +1,31 @@
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import requests
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import Users, fn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class HTTPHandlerPage(BaseHandler):
|
||||
def get(self, page):
|
||||
def get(self):
|
||||
url = self.request.full_url
|
||||
port = 443
|
||||
print(url)
|
||||
if url[len(url)-1] == '/':
|
||||
url = url.strip(url[len(url)-1])
|
||||
url_list = url.split('/')
|
||||
print(url_list)
|
||||
if url_list[0] != "":
|
||||
primary_url = url_list[0] + ":"+str(port)+"/"
|
||||
backup_url = url_list[0] + ":" +str(helper.get_setting["https_port"]) +"/"
|
||||
backup_url = url_list[0] + ":" +str(helper.get_setting("https_port")) +"/"
|
||||
for i in range(len(url_list)-1):
|
||||
primary_url += url_list[i+1]
|
||||
backup_url += url_list[i+1]
|
||||
else:
|
||||
primary_url = url + str(port)
|
||||
backup_url = url + str(helper.get_setting['https_port'])
|
||||
|
||||
backup_url = url + str(helper.get_setting('https_port'))
|
||||
|
||||
try:
|
||||
resp = requests.get(primary_url)
|
||||
resp.raise_for_status()
|
||||
url = primary_url
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
url = backup_url
|
||||
self.redirect('https://'+url+':'+ str(port))
|
||||
self.redirect('https://'+url+':'+ str(port))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,30 +1,22 @@
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import fn
|
||||
|
||||
from app.classes.models.users import Users
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from app.classes.shared.authentication import authentication
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.main_models import fn
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PublicHandler(BaseHandler):
|
||||
|
||||
def set_current_user(self, user):
|
||||
def set_current_user(self, user_id: str = None):
|
||||
|
||||
expire_days = helper.get_setting('cookie_expire')
|
||||
|
||||
@ -32,21 +24,21 @@ class PublicHandler(BaseHandler):
|
||||
if not expire_days:
|
||||
expire_days = "5"
|
||||
|
||||
if user:
|
||||
self.set_secure_cookie("user", tornado.escape.json_encode(user), expires_days=int(expire_days))
|
||||
if user_id is not None:
|
||||
self.set_cookie("token", authentication.generate(user_id), expires_days=int(expire_days))
|
||||
else:
|
||||
self.clear_cookie("user")
|
||||
|
||||
def get(self, page=None):
|
||||
|
||||
error = bleach.clean(self.get_argument('error', "Invalid Login!"))
|
||||
error_msg = bleach.clean(self.get_argument('error_msg', ''))
|
||||
|
||||
page_data = {
|
||||
'version': helper.get_version_string(),
|
||||
'error': error
|
||||
}
|
||||
|
||||
page_data['lang'] = tornado.locale.get("en_EN")
|
||||
'error': error, 'lang': helper.get_setting('language'),
|
||||
'lang_page': helper.getLangPage(helper.get_setting('language'))
|
||||
}
|
||||
|
||||
# sensible defaults
|
||||
template = "public/404.html"
|
||||
@ -75,6 +67,7 @@ class PublicHandler(BaseHandler):
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
error_msg = error_msg
|
||||
)
|
||||
|
||||
def post(self, page=None):
|
||||
@ -85,30 +78,32 @@ class PublicHandler(BaseHandler):
|
||||
entered_username = bleach.clean(self.get_argument('username'))
|
||||
entered_password = bleach.clean(self.get_argument('password'))
|
||||
|
||||
# pylint: disable=no-member
|
||||
user_data = Users.get_or_none(fn.Lower(Users.username) == entered_username.lower())
|
||||
|
||||
|
||||
# if we don't have a user
|
||||
if not user_data:
|
||||
next_page = "/public/error?error=Login Failed"
|
||||
error_msg = "Incorrect username or password. Please try again."
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
self.redirect(next_page)
|
||||
self.redirect(f'/public/login?error_msg={error_msg}')
|
||||
return
|
||||
|
||||
# if they are disabled
|
||||
if not user_data.enabled:
|
||||
next_page = "/public/error?error=Login Failed"
|
||||
error_msg = "User account disabled. Please contact your system administrator for more info."
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
self.redirect(next_page)
|
||||
self.redirect(f'/public/login?error_msg={error_msg}')
|
||||
return
|
||||
|
||||
login_result = helper.verify_pass(entered_password, user_data.password)
|
||||
|
||||
# Valid Login
|
||||
if login_result:
|
||||
self.set_current_user(entered_username)
|
||||
logger.info("User: {} Logged in from IP: {}".format(user_data, self.get_remote_ip()))
|
||||
self.set_current_user(user_data.user_id)
|
||||
logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}")
|
||||
|
||||
# record this login
|
||||
q = Users.select().where(Users.username == entered_username.lower()).get()
|
||||
@ -119,22 +114,14 @@ class PublicHandler(BaseHandler):
|
||||
# log this login
|
||||
self.controller.management.add_to_audit_log(user_data.user_id, "Logged in", 0, self.get_remote_ip())
|
||||
|
||||
cookie_data = {
|
||||
"username": user_data.username,
|
||||
"user_id": user_data.user_id,
|
||||
"account_type": user_data.superuser,
|
||||
}
|
||||
|
||||
self.set_secure_cookie('user_data', json.dumps(cookie_data))
|
||||
|
||||
next_page = "/panel/dashboard"
|
||||
self.redirect(next_page)
|
||||
else:
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
error_msg = "Inncorrect username or password. Please try again."
|
||||
# log this failed login attempt
|
||||
self.controller.management.add_to_audit_log(user_data.user_id, "Tried to log in", 0, self.get_remote_ip())
|
||||
self.redirect('/public/error?error=Login Failed')
|
||||
self.redirect(f'/public/login?error_msg={error_msg}')
|
||||
else:
|
||||
self.redirect("/public/login")
|
||||
|
||||
|
@ -1,40 +1,37 @@
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from app.classes.shared.file_helpers import file_helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
try:
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import bleach
|
||||
import libgravatar
|
||||
import requests
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ServerHandler(BaseHandler):
|
||||
|
||||
@tornado.web.authenticated
|
||||
def get(self, page):
|
||||
# name = tornado.escape.json_decode(self.current_user)
|
||||
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
exec_user_id = exec_user_data['user_id']
|
||||
exec_user = self.controller.users.get_user_by_id(exec_user_id)
|
||||
# pylint: disable=unused-variable
|
||||
api_key, token_data, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
exec_user_role = set()
|
||||
if exec_user['superuser'] == 1:
|
||||
if superuser:
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
exec_user_role.add("Super User")
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
|
||||
@ -42,8 +39,8 @@ class ServerHandler(BaseHandler):
|
||||
for role in self.controller.roles.get_all_roles():
|
||||
list_roles.append(self.controller.roles.get_role(role.role_id))
|
||||
else:
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user_id)
|
||||
defined_servers = self.controller.servers.get_authorized_servers(exec_user_id)
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user["user_id"])
|
||||
defined_servers = self.controller.servers.get_authorized_servers(exec_user["user_id"])
|
||||
list_roles = []
|
||||
for r in exec_user['roles']:
|
||||
role = self.controller.roles.get_role(r)
|
||||
@ -54,7 +51,7 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
page_data = {
|
||||
'version_data': helper.get_version_string(),
|
||||
'user_data': exec_user_data,
|
||||
'user_data': exec_user,
|
||||
'user_role' : exec_user_role,
|
||||
'roles' : list_roles,
|
||||
'user_crafty_permissions' : exec_user_crafty_permissions,
|
||||
@ -71,20 +68,54 @@ class ServerHandler(BaseHandler):
|
||||
'hosts_data': self.controller.management.get_latest_hosts_stats(),
|
||||
'menu_servers': defined_servers,
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True),
|
||||
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
|
||||
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
|
||||
'lang_page': helper.getLangPage(self.controller.users.get_user_lang_by_id(exec_user["user_id"])),
|
||||
'api_key': {
|
||||
'name': api_key.name,
|
||||
'created': api_key.created,
|
||||
'server_permissions': api_key.server_permissions,
|
||||
'crafty_permissions': api_key.crafty_permissions,
|
||||
'superuser': api_key.superuser
|
||||
} if api_key is not None else None,
|
||||
'superuser': superuser
|
||||
}
|
||||
if exec_user['superuser'] == 1:
|
||||
|
||||
if helper.get_setting("allow_nsfw_profile_pictures"):
|
||||
rating = "x"
|
||||
else:
|
||||
rating = "g"
|
||||
|
||||
|
||||
if exec_user['email'] != 'default@example.com' or "":
|
||||
g = libgravatar.Gravatar(libgravatar.sanitize_email(exec_user['email']))
|
||||
url = g.get_image(size=80, default="404", force_default=False, rating=rating, filetype_extension=False, use_ssl=True) # + "?d=404"
|
||||
if requests.head(url).status_code != 404:
|
||||
profile_url = url
|
||||
else:
|
||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
||||
else:
|
||||
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
|
||||
|
||||
page_data['user_image'] = profile_url
|
||||
if superuser:
|
||||
page_data['roles'] = list_roles
|
||||
|
||||
if page == "step1":
|
||||
if not exec_user['superuser'] and not self.controller.crafty_perms.can_create_server(exec_user_id):
|
||||
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
|
||||
return
|
||||
|
||||
page_data['server_types'] = server_jar_obj.get_serverjar_data_sorted()
|
||||
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data_sorted())
|
||||
page_data['server_types'] = server_jar_obj.get_serverjar_data()
|
||||
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data())
|
||||
template = "server/wizard.html"
|
||||
|
||||
if page == "bedrock_step1":
|
||||
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
|
||||
return
|
||||
|
||||
template = "server/bedrock_wizard.html"
|
||||
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
@ -93,17 +124,19 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
@tornado.web.authenticated
|
||||
def post(self, page):
|
||||
|
||||
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
exec_user_id = exec_user_data['user_id']
|
||||
exec_user = self.controller.users.get_user_by_id(exec_user_id)
|
||||
# pylint: disable=unused-variable
|
||||
api_key, token_data, exec_user = self.current_user
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
|
||||
template = "public/404.html"
|
||||
page_data = {
|
||||
'version_data': "version_data_here",
|
||||
'user_data': exec_user_data,
|
||||
'version_data': "version_data_here", # TODO
|
||||
'user_data': exec_user,
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True),
|
||||
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
|
||||
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
|
||||
'lang_page': helper.getLangPage(self.controller.users.get_user_lang_by_id(exec_user["user_id"]))
|
||||
}
|
||||
|
||||
if page == "command":
|
||||
@ -125,7 +158,7 @@ class ServerHandler(BaseHandler):
|
||||
name_counter = 1
|
||||
while is_name_used(new_server_name):
|
||||
name_counter += 1
|
||||
new_server_name = server_data.get('server_name') + " (Copy {})".format(name_counter)
|
||||
new_server_name = server_data.get('server_name') + f" (Copy {name_counter})"
|
||||
|
||||
new_server_uuid = helper.create_uuid()
|
||||
while os.path.exists(os.path.join(helper.servers_dir, new_server_uuid)):
|
||||
@ -133,29 +166,36 @@ class ServerHandler(BaseHandler):
|
||||
new_server_path = os.path.join(helper.servers_dir, new_server_uuid)
|
||||
|
||||
# copy the old server
|
||||
shutil.copytree(server_data.get('path'), new_server_path)
|
||||
file_helper.copy_dir(server_data.get('path'), new_server_path)
|
||||
|
||||
# TODO get old server DB data to individual variables
|
||||
stop_command = server_data.get('stop_command')
|
||||
new_server_command = str(server_data.get('execution_command')).replace(server_uuid, new_server_uuid)
|
||||
new_executable = server_data.get('executable')
|
||||
new_server_log_file = str(server_data.get('log_path')).replace(server_uuid, new_server_uuid)
|
||||
auto_start = server_data.get('auto_start')
|
||||
auto_start_delay = server_data.get('auto_start_delay')
|
||||
crash_detection = server_data.get('crash_detection')
|
||||
new_server_log_file = str(helper.get_os_understandable_path(server_data.get('log_path'))).replace(server_uuid, new_server_uuid)
|
||||
server_port = server_data.get('server_port')
|
||||
server_type = server_data.get('type')
|
||||
|
||||
self.controller.servers.create_server(new_server_name, new_server_uuid, new_server_path, "", new_server_command, new_executable, new_server_log_file, stop_command, server_port)
|
||||
self.controller.servers.create_server(new_server_name,
|
||||
new_server_uuid,
|
||||
new_server_path,
|
||||
"",
|
||||
new_server_command,
|
||||
new_executable,
|
||||
new_server_log_file,
|
||||
stop_command,
|
||||
server_type,
|
||||
server_port)
|
||||
|
||||
self.controller.init_all_servers()
|
||||
|
||||
return
|
||||
|
||||
self.controller.management.send_command(exec_user_data['user_id'], server_id, self.get_remote_ip(), command)
|
||||
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), command)
|
||||
|
||||
if page == "step1":
|
||||
|
||||
if not exec_user['superuser']:
|
||||
if not superuser:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
else:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
@ -171,7 +211,7 @@ class ServerHandler(BaseHandler):
|
||||
captured_roles = []
|
||||
for role in user_roles:
|
||||
if bleach.clean(self.get_argument(str(role), '')) == "on":
|
||||
captured_roles.append(role)
|
||||
captured_roles.append(role)
|
||||
|
||||
if not server_name:
|
||||
self.redirect("/panel/error?error=Server name cannot be empty!")
|
||||
@ -185,46 +225,51 @@ class ServerHandler(BaseHandler):
|
||||
return
|
||||
|
||||
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
|
||||
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
|
||||
"imported a jar server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"imported a jar server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
elif import_type == 'import_zip':
|
||||
# here import_server_path means the zip path
|
||||
good_path = self.controller.verify_zip_server(import_server_path)
|
||||
zip_path = bleach.clean(self.get_argument('root_path'))
|
||||
good_path = helper.check_path_exists(zip_path)
|
||||
if not good_path:
|
||||
self.redirect("/panel/error?error=Zip file not found!")
|
||||
self.redirect("/panel/error?error=Temp path not found!")
|
||||
return
|
||||
|
||||
new_server_id = self.controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
|
||||
new_server_id = self.controller.import_zip_server(server_name, zip_path, import_server_jar, min_mem, max_mem, port)
|
||||
if new_server_id == "false":
|
||||
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
|
||||
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with" +
|
||||
f"sudo chown -R crafty:crafty {import_server_path} And sudo chmod 2775 -R {import_server_path}")
|
||||
return
|
||||
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
|
||||
"imported a zip server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"imported a zip server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
#deletes temp dir
|
||||
file_helper.del_dirs(zip_path)
|
||||
else:
|
||||
if len(server_parts) != 2:
|
||||
self.redirect("/panel/error?error=Invalid server data")
|
||||
return
|
||||
server_type, server_version = server_parts
|
||||
# TODO: add server type check here and call the correct server add functions if not a jar
|
||||
role_ids = self.controller.users.get_user_roles_id(exec_user_id)
|
||||
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"])
|
||||
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
|
||||
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
|
||||
"created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"created a {server_version} {str(server_type).capitalize()} server named \"{server_name}\"",
|
||||
# Example: Admin created a 1.16.5 Bukkit server named "survival"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
|
||||
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
|
||||
if len(captured_roles) == 0:
|
||||
if not exec_user['superuser']:
|
||||
if not superuser:
|
||||
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
|
||||
role_id = self.controller.roles.add_role("Creator of Server with uuid={}".format(new_server_uuid))
|
||||
role_id = self.controller.roles.add_role(f"Creator of Server with uuid={new_server_uuid}")
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
self.controller.users.add_role_to_user(exec_user_id, role_id)
|
||||
self.controller.crafty_perms.add_server_creation(exec_user_id)
|
||||
self.controller.users.add_role_to_user(exec_user["user_id"], role_id)
|
||||
self.controller.crafty_perms.add_server_creation(exec_user["user_id"])
|
||||
|
||||
else:
|
||||
for role in captured_roles:
|
||||
@ -234,8 +279,94 @@ class ServerHandler(BaseHandler):
|
||||
self.controller.stats.record_stats()
|
||||
self.redirect("/panel/dashboard")
|
||||
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
)
|
||||
if page == "bedrock_step1":
|
||||
if not superuser:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
else:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
server = bleach.clean(self.get_argument('server', ''))
|
||||
server_name = bleach.clean(self.get_argument('server_name', ''))
|
||||
port = bleach.clean(self.get_argument('port', ''))
|
||||
import_type = bleach.clean(self.get_argument('create_type', ''))
|
||||
import_server_path = bleach.clean(self.get_argument('server_path', ''))
|
||||
import_server_exe = bleach.clean(self.get_argument('server_jar', ''))
|
||||
server_parts = server.split("|")
|
||||
captured_roles = []
|
||||
for role in user_roles:
|
||||
if bleach.clean(self.get_argument(str(role), '')) == "on":
|
||||
captured_roles.append(role)
|
||||
|
||||
if not server_name:
|
||||
self.redirect("/panel/error?error=Server name cannot be empty!")
|
||||
return
|
||||
|
||||
if import_type == 'import_jar':
|
||||
good_path = self.controller.verify_jar_server(import_server_path, import_server_exe)
|
||||
|
||||
if not good_path:
|
||||
self.redirect("/panel/error?error=Server path or Server Jar not found!")
|
||||
return
|
||||
|
||||
new_server_id = self.controller.import_bedrock_server(server_name, import_server_path,import_server_exe, port)
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"imported a jar server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
elif import_type == 'import_zip':
|
||||
# here import_server_path means the zip path
|
||||
zip_path = bleach.clean(self.get_argument('root_path'))
|
||||
good_path = helper.check_path_exists(zip_path)
|
||||
if not good_path:
|
||||
self.redirect("/panel/error?error=Temp path not found!")
|
||||
return
|
||||
|
||||
new_server_id = self.controller.import_bedrock_zip_server(server_name, zip_path, import_server_exe, port)
|
||||
if new_server_id == "false":
|
||||
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with" +
|
||||
f"sudo chown -R crafty:crafty {import_server_path} And sudo chmod 2775 -R {import_server_path}")
|
||||
return
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"imported a zip server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
#deletes temp dir
|
||||
file_helper.del_dirs(zip_path)
|
||||
else:
|
||||
if len(server_parts) != 2:
|
||||
self.redirect("/panel/error?error=Invalid server data")
|
||||
return
|
||||
server_type, server_version = server_parts
|
||||
# TODO: add server type check here and call the correct server add functions if not a jar
|
||||
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"])
|
||||
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
f"created a {server_version} {str(server_type).capitalize()} server named \"{server_name}\"",
|
||||
# Example: Admin created a 1.16.5 Bukkit server named "survival"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
|
||||
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
|
||||
if len(captured_roles) == 0:
|
||||
if not superuser:
|
||||
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
|
||||
role_id = self.controller.roles.add_role(f"Creator of Server with uuid={new_server_uuid}")
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
self.controller.users.add_role_to_user(exec_user["user_id"], role_id)
|
||||
self.controller.crafty_perms.add_server_creation(exec_user["user_id"])
|
||||
|
||||
else:
|
||||
for role in captured_roles:
|
||||
role_id = role
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
|
||||
self.controller.stats.record_stats()
|
||||
self.redirect("/panel/dashboard")
|
||||
|
||||
try:
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
)
|
||||
except RuntimeError:
|
||||
self.redirect('/panel/dashboard')
|
||||
|
@ -1,9 +1,11 @@
|
||||
import tornado.web
|
||||
from typing import (
|
||||
Optional
|
||||
)
|
||||
from typing import ( Optional )
|
||||
|
||||
from app.classes.shared.console import console
|
||||
try:
|
||||
import tornado.web
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
from app.classes.shared.helpers import helper
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
class CustomStaticHandler(tornado.web.StaticFileHandler):
|
||||
def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
|
||||
@ -12,4 +14,4 @@ class CustomStaticHandler(tornado.web.StaticFileHandler):
|
||||
except tornado.web.HTTPError as error:
|
||||
if 'HTTP 404: Not Found' in str(error):
|
||||
self.set_status(404)
|
||||
self.finish({'error':'NOT_FOUND', 'info':'The requested resource was not found on the server'})
|
||||
self.finish({'error':'NOT_FOUND', 'info':'The requested resource was not found on the server'})
|
||||
|
@ -1,36 +1,27 @@
|
||||
from re import template
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import requests
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import fn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class StatusHandler(BaseHandler):
|
||||
def get(self):
|
||||
page_data = {}
|
||||
page_data['lang'] = tornado.locale.get("en_EN")
|
||||
page_data['lang'] = helper.get_setting('language')
|
||||
page_data['lang_page'] = helper.getLangPage(helper.get_setting('language'))
|
||||
page_data['servers'] = self.controller.servers.get_all_servers_stats()
|
||||
running = 0
|
||||
for srv in page_data['servers']:
|
||||
if srv['stats']['running']:
|
||||
running += 1
|
||||
server_data = srv.get('server_data', False)
|
||||
server_id = server_data.get('server_id', False)
|
||||
srv['raw_ping_result'] = self.controller.stats.get_raw_server_stats(server_id)
|
||||
srv['raw_ping_result'] = self.controller.servers.get_server_stats_by_id(server_id)
|
||||
if 'icon' not in srv['raw_ping_result']:
|
||||
srv['raw_ping_result']['icon'] = False
|
||||
|
||||
page_data['running'] = running
|
||||
|
||||
template = 'public/status.html'
|
||||
|
||||
@ -45,12 +36,11 @@ class StatusHandler(BaseHandler):
|
||||
for srv in page_data['servers']:
|
||||
server_data = srv.get('server_data', False)
|
||||
server_id = server_data.get('server_id', False)
|
||||
srv['raw_ping_result'] = self.controller.stats.get_raw_server_stats(server_id)
|
||||
|
||||
srv['raw_ping_result'] = self.controller.servers.get_server_stats_by_id(server_id)
|
||||
template = 'public/status.html'
|
||||
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
)
|
||||
)
|
||||
|
@ -3,12 +3,22 @@ import sys
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from app.classes.shared.translation import translation
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from app.classes.web.file_handler import FileHandler
|
||||
from app.classes.web.public_handler import PublicHandler
|
||||
from app.classes.web.panel_handler import PanelHandler
|
||||
from app.classes.web.default_handler import DefaultHandler
|
||||
from app.classes.web.server_handler import ServerHandler
|
||||
from app.classes.web.ajax_handler import AjaxHandler
|
||||
from app.classes.web.api_handler import ServersStats, NodeStats
|
||||
from app.classes.web.websocket_handler import SocketHandler
|
||||
from app.classes.web.static_handler import CustomStaticHandler
|
||||
from app.classes.web.upload_handler import UploadHandler
|
||||
from app.classes.web.http_handler import HTTPHandler, HTTPHandlerPage
|
||||
from app.classes.web.status_handler import StatusHandler
|
||||
|
||||
try:
|
||||
import tornado.web
|
||||
@ -18,25 +28,11 @@ try:
|
||||
import tornado.escape
|
||||
import tornado.locale
|
||||
import tornado.httpserver
|
||||
from app.classes.web.public_handler import PublicHandler
|
||||
from app.classes.web.panel_handler import PanelHandler
|
||||
from app.classes.web.default_handler import DefaultHandler
|
||||
from app.classes.web.server_handler import ServerHandler
|
||||
from app.classes.web.ajax_handler import AjaxHandler
|
||||
from app.classes.web.api_handler import ServersStats, NodeStats
|
||||
from app.classes.web.websocket_handler import SocketHandler
|
||||
from app.classes.web.static_handler import CustomStaticHandler
|
||||
from app.classes.shared.translation import translation
|
||||
from app.classes.web.upload_handler import UploadHandler
|
||||
from app.classes.web.http_handler import HTTPHandler, HTTPHandlerPage
|
||||
from app.classes.web.status_handler import StatusHandler
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Webserver:
|
||||
|
||||
@ -48,7 +44,6 @@ class Webserver:
|
||||
self.tasks_manager = tasks_manager
|
||||
self._asyncio_patch()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def log_function(handler):
|
||||
|
||||
@ -57,6 +52,7 @@ class Webserver:
|
||||
'Method': handler.request.method,
|
||||
'URL': handler.request.uri,
|
||||
'Remote_IP': handler.request.remote_ip,
|
||||
# pylint: disable=consider-using-f-string
|
||||
'Elapsed_Time': '%.2fms' % (handler.request.request_time() * 1000)
|
||||
}
|
||||
|
||||
@ -74,12 +70,12 @@ class Webserver:
|
||||
"""
|
||||
logger.debug("Checking if asyncio patch is required")
|
||||
if sys.platform.startswith("win") and sys.version_info >= (3, 8):
|
||||
# pylint: disable=reimported,import-outside-toplevel,redefined-outer-name
|
||||
import asyncio
|
||||
try:
|
||||
from asyncio import WindowsSelectorEventLoopPolicy
|
||||
except ImportError:
|
||||
logger.debug("asyncio patch isn't required")
|
||||
pass # Can't assign a policy which doesn't exist.
|
||||
logger.debug("asyncio patch isn't required") # Can't assign a policy which doesn't exist.
|
||||
else:
|
||||
if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy):
|
||||
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
|
||||
@ -92,7 +88,7 @@ class Webserver:
|
||||
|
||||
http_port = helper.get_setting('http_port')
|
||||
https_port = helper.get_setting('https_port')
|
||||
|
||||
|
||||
debug_errors = helper.get_setting('show_errors')
|
||||
cookie_secret = helper.get_setting('cookie_secret')
|
||||
|
||||
@ -110,12 +106,13 @@ class Webserver:
|
||||
'keyfile': os.path.join(helper.config_dir, 'web', 'certs', 'commander.key.pem'),
|
||||
}
|
||||
|
||||
logger.info("Starting Web Server on ports http:{} https:{}".format(http_port, https_port))
|
||||
logger.info(f"Starting Web Server on ports http:{http_port} https:{https_port}")
|
||||
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
|
||||
tornado.template.Loader('.')
|
||||
|
||||
# TODO: Remove because we don't and won't use
|
||||
tornado.locale.set_default_locale('en_EN')
|
||||
|
||||
handler_args = {"controller": self.controller, "tasks_manager": self.tasks_manager, "translator": translation}
|
||||
@ -125,6 +122,7 @@ class Webserver:
|
||||
(r'/panel/(.*)', PanelHandler, handler_args),
|
||||
(r'/server/(.*)', ServerHandler, handler_args),
|
||||
(r'/ajax/(.*)', AjaxHandler, handler_args),
|
||||
(r'/files/(.*)', FileHandler, handler_args),
|
||||
(r'/api/stats/servers', ServersStats, handler_args),
|
||||
(r'/api/stats/node', NodeStats, handler_args),
|
||||
(r'/ws', SocketHandler, handler_args),
|
||||
@ -175,10 +173,8 @@ class Webserver:
|
||||
self.HTTPS_Server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects)
|
||||
self.HTTPS_Server.listen(https_port)
|
||||
|
||||
logger.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
|
||||
logger.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
|
||||
console.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
|
||||
console.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
|
||||
logger.info(f"https://{helper.get_local_ip()}:{https_port} is up and ready for connections.")
|
||||
console.info(f"https://{helper.get_local_ip()}:{https_port} is up and ready for connections.")
|
||||
|
||||
console.info("Server Init Complete: Listening For Connections:")
|
||||
|
@ -1,25 +1,31 @@
|
||||
from app.classes.shared.main_controller import Controller
|
||||
import tornado.options
|
||||
import tornado.web
|
||||
import tornado.httpserver
|
||||
from tornado.options import options
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.shared.console import console
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_controller import Controller
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
try:
|
||||
import tornado.web
|
||||
import tornado.options
|
||||
import tornado.httpserver
|
||||
|
||||
except ModuleNotFoundError as ex:
|
||||
helper.auto_installer_fix(ex)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Class&Function Defination
|
||||
# Class & Function Defination
|
||||
MAX_STREAMED_SIZE = 1024 * 1024 * 1024
|
||||
|
||||
@tornado.web.stream_request_body
|
||||
class UploadHandler(tornado.web.RequestHandler):
|
||||
class UploadHandler(BaseHandler):
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
def initialize(self, controller: Controller=None, tasks_manager=None, translator=None):
|
||||
self.controller = controller
|
||||
self.tasks_manager = tasks_manager
|
||||
@ -27,8 +33,21 @@ class UploadHandler(tornado.web.RequestHandler):
|
||||
|
||||
def prepare(self):
|
||||
self.do_upload = True
|
||||
user_data = json.loads(self.get_secure_cookie('user_data'))
|
||||
user_id = user_data['user_id']
|
||||
# pylint: disable=unused-variable
|
||||
api_key, token_data, exec_user = self.current_user
|
||||
server_id = self.get_argument('server_id', None)
|
||||
superuser = exec_user['superuser']
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
user_id = exec_user['user_id']
|
||||
|
||||
if superuser:
|
||||
exec_user_server_permissions = self.controller.server_perms.list_defined_permissions()
|
||||
elif api_key is not None:
|
||||
exec_user_server_permissions = self.controller.server_perms.get_api_key_permissions_list(api_key, server_id)
|
||||
else:
|
||||
exec_user_server_permissions = self.controller.server_perms.get_user_id_permissions_list(
|
||||
exec_user["user_id"], server_id)
|
||||
|
||||
server_id = self.request.headers.get('X-ServerId', None)
|
||||
|
||||
@ -42,8 +61,7 @@ class UploadHandler(tornado.web.RequestHandler):
|
||||
console.warning('Server ID not found in upload handler call')
|
||||
self.do_upload = False
|
||||
|
||||
user_permissions = self.controller.server_perms.get_user_permissions_list(user_id, server_id)
|
||||
if Enum_Permissions_Server.Files not in user_permissions:
|
||||
if Enum_Permissions_Server.Files not in exec_user_server_permissions:
|
||||
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
|
||||
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
|
||||
self.do_upload = False
|
||||
@ -52,8 +70,8 @@ class UploadHandler(tornado.web.RequestHandler):
|
||||
filename = self.request.headers.get('X-FileName', None)
|
||||
full_path = os.path.join(path, filename)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], full_path):
|
||||
print(user_id, server_id, self.controller.servers.get_server_data_by_id(server_id)['path'], full_path)
|
||||
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path):
|
||||
print(user_id, server_id, helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path)
|
||||
logger.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
|
||||
console.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
|
||||
self.do_upload = False
|
||||
@ -62,7 +80,7 @@ class UploadHandler(tornado.web.RequestHandler):
|
||||
try:
|
||||
self.f = open(full_path, "wb")
|
||||
except Exception as e:
|
||||
logger.error("Upload failed with error: {}".format(e))
|
||||
logger.error(f"Upload failed with error: {e}")
|
||||
self.do_upload = False
|
||||
# If max_body_size is not set, you cannot upload files > 100MB
|
||||
self.request.connection.set_max_body_size(MAX_STREAMED_SIZE)
|
||||
@ -83,6 +101,6 @@ class UploadHandler(tornado.web.RequestHandler):
|
||||
websocket_helper.broadcast('close_upload_box', 'error')
|
||||
self.finish('error')
|
||||
|
||||
def data_received(self, data):
|
||||
def data_received(self, chunk):
|
||||
if self.do_upload:
|
||||
self.f.write(data)
|
||||
self.f.write(chunk)
|
||||
|
@ -1,23 +1,27 @@
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from urllib.parse import parse_qsl
|
||||
from app.classes.models.users import Users
|
||||
|
||||
from app.classes.shared.authentication import authentication
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import tornado.websocket
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
helper.auto_installer_fix(e)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
page = None
|
||||
page_query_params = None
|
||||
controller = None
|
||||
tasks_manager = None
|
||||
translator = None
|
||||
io_loop = None
|
||||
|
||||
def initialize(self, controller=None, tasks_manager=None, translator=None):
|
||||
self.controller = controller
|
||||
@ -31,18 +35,14 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
self.request.remote_ip
|
||||
return remote_ip
|
||||
|
||||
def get_user_id(self):
|
||||
_, _, user = authentication.check(self.get_cookie('token'))
|
||||
return user['user_id']
|
||||
|
||||
def check_auth(self):
|
||||
user_data_cookie_raw = self.get_secure_cookie('user_data')
|
||||
|
||||
if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'):
|
||||
user_data_cookie = user_data_cookie_raw.decode('utf-8')
|
||||
user_id = json.loads(user_data_cookie)['user_id']
|
||||
query = Users.select().where(Users.user_id == user_id)
|
||||
if query.exists():
|
||||
return True
|
||||
return False
|
||||
|
||||
return authentication.check_bool(self.get_cookie('token'))
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def open(self):
|
||||
logger.debug('Checking WebSocket authentication')
|
||||
if self.check_auth():
|
||||
@ -50,7 +50,10 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
else:
|
||||
websocket_helper.send_message(self, 'notification', 'Not authenticated for WebSocket connection')
|
||||
self.close()
|
||||
self.controller.management.add_to_audit_log_raw('unknown', 0, 0, 'Someone tried to connect via WebSocket without proper authentication', self.get_remote_ip())
|
||||
self.controller.management.add_to_audit_log_raw('unknown',
|
||||
0, 0,
|
||||
'Someone tried to connect via WebSocket without proper authentication',
|
||||
self.get_remote_ip())
|
||||
websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication')
|
||||
logger.warning('Someone tried to connect via WebSocket without proper authentication')
|
||||
|
||||
@ -62,22 +65,21 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
)))
|
||||
websocket_helper.add_client(self)
|
||||
logger.debug('Opened WebSocket connection')
|
||||
# websocket_helper.broadcast('notification', 'New client connected')
|
||||
|
||||
def on_message(self, rawMessage):
|
||||
# pylint: disable=arguments-renamed
|
||||
@staticmethod
|
||||
def on_message(raw_message):
|
||||
|
||||
logger.debug('Got message from WebSocket connection {}'.format(rawMessage))
|
||||
message = json.loads(rawMessage)
|
||||
logger.debug('Event Type: {}, Data: {}'.format(message['event'], message['data']))
|
||||
logger.debug(f'Got message from WebSocket connection {raw_message}')
|
||||
message = json.loads(raw_message)
|
||||
logger.debug(f"Event Type: {message['event']}, Data: {message['data']}")
|
||||
|
||||
def on_close(self):
|
||||
websocket_helper.remove_client(self)
|
||||
logger.debug('Closed WebSocket connection')
|
||||
# websocket_helper.broadcast('notification', 'Client disconnected')
|
||||
|
||||
async def write_message_int(self, message):
|
||||
self.write_message(message)
|
||||
|
||||
|
||||
def write_message_helper(self, message):
|
||||
asyncio.run_coroutine_threadsafe(self.write_message_int(message), self.io_loop.asyncio_loop)
|
||||
|
||||
|
@ -1,57 +1,69 @@
|
||||
import json
|
||||
import logging
|
||||
import sys, threading, asyncio
|
||||
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
try:
|
||||
import tornado.ioloop
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
class WebSocketHelper:
|
||||
def __init__(self):
|
||||
self.clients = set()
|
||||
|
||||
def add_client(self, client):
|
||||
self.clients.add(client)
|
||||
|
||||
|
||||
def remove_client(self, client):
|
||||
self.clients.remove(client)
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def send_message(self, client, event_type: str, data):
|
||||
if client.check_auth():
|
||||
message = str(json.dumps({'event': event_type, 'data': data}))
|
||||
client.write_message_helper(message)
|
||||
|
||||
def broadcast(self, event_type: str, data):
|
||||
logger.debug('Sending to {} clients: {}'.format(len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
logger.debug(f"Sending to {len(self.clients)} clients: {json.dumps({'event': event_type, 'data': data})}")
|
||||
for client in self.clients:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except Exception as e:
|
||||
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
|
||||
logger.exception(f'Error caught while sending WebSocket message to {client.get_remote_ip()} {e}')
|
||||
|
||||
def broadcast_page(self, page: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
return client.page == page
|
||||
|
||||
clients = list(filter(filter_fn, self.clients))
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
def broadcast_user(self, user_id: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
return client.get_user_id() == user_id
|
||||
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
def broadcast_user_page(self, page: str, user_id: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if client.get_user_id() != user_id:
|
||||
return False
|
||||
if client.page != page:
|
||||
return False
|
||||
return True
|
||||
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
def broadcast_user_page_params(self, page: str, params: dict, user_id: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if client.get_user_id() != user_id:
|
||||
return False
|
||||
if client.page != page:
|
||||
return False
|
||||
for key, param in params.items():
|
||||
if param != client.page_query_params.get(key, None):
|
||||
return False
|
||||
return True
|
||||
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
for client in clients:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except Exception as e:
|
||||
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
|
||||
|
||||
def broadcast_page_params(self, page: str, params: dict, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if client.page != page:
|
||||
@ -61,20 +73,22 @@ class WebSocketHelper:
|
||||
return False
|
||||
return True
|
||||
|
||||
clients = list(filter(filter_fn, self.clients))
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
def broadcast_with_fn(self, filter_fn, event_type: str, data):
|
||||
clients = list(filter(filter_fn, self.clients))
|
||||
logger.debug(f"Sending to {len(clients)} out of {len(self.clients)} clients: {json.dumps({'event': event_type, 'data': data})}")
|
||||
|
||||
for client in clients:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except Exception as e:
|
||||
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
|
||||
|
||||
logger.exception(f'Error catched while sending WebSocket message to {client.get_remote_ip()} {e}')
|
||||
|
||||
def disconnect_all(self):
|
||||
console.info('Disconnecting WebSocket clients')
|
||||
for client in self.clients:
|
||||
client.close()
|
||||
console.info('Disconnected WebSocket clients')
|
||||
|
||||
websocket_helper = WebSocketHelper()
|
||||
websocket_helper = WebSocketHelper()
|
||||
|
@ -5,6 +5,7 @@
|
||||
"language": "en_EN",
|
||||
"cookie_expire": 30,
|
||||
"cookie_secret": "random",
|
||||
"apikey_secret": "random",
|
||||
"show_errors": true,
|
||||
"history_max_age": 7,
|
||||
"stats_update_frequency": 30,
|
||||
@ -12,5 +13,8 @@
|
||||
"show_contribute_link": true,
|
||||
"virtual_terminal_lines": 70,
|
||||
"max_log_lines": 700,
|
||||
"keywords": ["help", "chunk"]
|
||||
"max_audit_entries": 300,
|
||||
"disabled_language_files": ["lol_EN.json", ""],
|
||||
"keywords": ["help", "chunk"],
|
||||
"allow_nsfw_profile_pictures": false
|
||||
}
|
||||
|
@ -1,301 +1,140 @@
|
||||
{
|
||||
"staff": {
|
||||
"development": [
|
||||
{
|
||||
"name": "Phil Tarrant",
|
||||
"title": "Creator",
|
||||
"loc": "Southeast, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Ptarrant1"
|
||||
],
|
||||
"Creator"
|
||||
],
|
||||
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
|
||||
"pic": "/static/assets/images/credits/ptarrant.png"
|
||||
},
|
||||
{
|
||||
"name": "Pita Bread",
|
||||
"title": "Leadership Team",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/craftbreadth"
|
||||
],
|
||||
"Community Leader"
|
||||
],
|
||||
"blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.",
|
||||
"pic": "/static/assets/images/credits/pita.png"
|
||||
},
|
||||
{
|
||||
"name": "macgeek",
|
||||
"title": "Leadership Team",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/computergeek125"
|
||||
],
|
||||
"Project Manager"
|
||||
],
|
||||
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
|
||||
"pic": "/static/assets/images/credits/macgeek.png"
|
||||
},
|
||||
{
|
||||
"name": "parzivaldewey",
|
||||
"title": null,
|
||||
"loc": "East Coast, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/amcmanu3"
|
||||
],
|
||||
"Support Manager"
|
||||
],
|
||||
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
|
||||
"pic": "/static/assets/images/credits/parzivaldewey.png"
|
||||
},
|
||||
{
|
||||
"name": "Xithical",
|
||||
"title": null,
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/xithical"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.",
|
||||
"pic": "/static/assets/images/credits/xithical.png"
|
||||
},
|
||||
{
|
||||
"name": "MC Gaming",
|
||||
"title": null,
|
||||
"loc": "Central, UK",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/MCgamin1738"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.",
|
||||
"pic": "/static/assets/images/credits/mcgaming.png"
|
||||
},
|
||||
{
|
||||
"name": "Silversthorn",
|
||||
"title": null,
|
||||
"loc": "Provence, FR",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Silversthorn"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Developper at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"",
|
||||
"pic": "/static/assets/images/credits/silversthorn.png"
|
||||
},
|
||||
{
|
||||
"name": "ThatOneLukas",
|
||||
"title": null,
|
||||
"loc": "Helsinki, FI",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/LukasDoesDev"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Lukas enjoys bashing his head at the table while his code does not work",
|
||||
"pic": "/static/assets/images/credits/lukas.png"
|
||||
},
|
||||
{
|
||||
"name": "Zedifus",
|
||||
"title": null,
|
||||
"loc": "Scotland, UK",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Zedifus"
|
||||
],
|
||||
"DevOps"
|
||||
],
|
||||
"blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.",
|
||||
"pic": "/static/assets/images/credits/zedifus.jpg"
|
||||
}
|
||||
],
|
||||
"support": [
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Wiki",
|
||||
null
|
||||
],
|
||||
"blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.",
|
||||
"pic": "/static/assets/images/credits/isilverfyre.png"
|
||||
},
|
||||
{
|
||||
"name": "Quentin",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Developer",
|
||||
null
|
||||
],
|
||||
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
|
||||
"pic": "/static/assets/images/credits/qub3d.png"
|
||||
}
|
||||
],
|
||||
"retired": [
|
||||
{
|
||||
"name": "Kev Dagoat",
|
||||
"title": "Head of Development",
|
||||
"loc": "East Coast, AU",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/kevdagoat"
|
||||
],
|
||||
"HOD"
|
||||
],
|
||||
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.",
|
||||
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
|
||||
},
|
||||
{
|
||||
"name": "Manu",
|
||||
"title": null,
|
||||
"loc": "Eastern, CA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Developer",
|
||||
"Project Manager"
|
||||
],
|
||||
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
|
||||
"pic": "/static/assets/images/credits/manu.png"
|
||||
},
|
||||
{
|
||||
"name": "UltraBlack",
|
||||
"title": null,
|
||||
"loc": "Bavaria, DE",
|
||||
"tags": [
|
||||
"Staff",
|
||||
null,
|
||||
"Idea Manager"
|
||||
],
|
||||
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
|
||||
"pic": "/static/assets/images/credits/ultrablack.png"
|
||||
}
|
||||
]
|
||||
"development": [
|
||||
{
|
||||
"name": "Pita Bread",
|
||||
"title": "CEO",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/craftbreadth" ], "Crafty Leadership" ],
|
||||
"blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.",
|
||||
"pic": "/static/assets/images/credits/pita.png"
|
||||
},
|
||||
{
|
||||
"name": "Xithical",
|
||||
"title": "CFO/COO",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/xithical" ], "Crafty Leadership" ],
|
||||
"blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.",
|
||||
"pic": "/static/assets/images/credits/xithical.png"
|
||||
},
|
||||
{
|
||||
"name": "Phil Tarrant",
|
||||
"title": "Creator",
|
||||
"loc": "Southeast, USA",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/Ptarrant1" ], "Crafty Advisor" ],
|
||||
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
|
||||
"pic": "/static/assets/images/credits/ptarrant.png"
|
||||
},
|
||||
{
|
||||
"name": "macgeek",
|
||||
"title": "Lead Software Engineer",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/computergeek125" ], "Crafty Advisor" ],
|
||||
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
|
||||
"pic": "/static/assets/images/credits/macgeek.png"
|
||||
},
|
||||
{
|
||||
"name": "parzivaldewey",
|
||||
"title": "Support Manager",
|
||||
"loc": "East Coast, USA",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/amcmanu3" ], "Support Manager" ],
|
||||
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
|
||||
"pic": "/static/assets/images/credits/parzivaldewey.png"
|
||||
},
|
||||
{
|
||||
"name": "Silversthorn",
|
||||
"title": "Software Engineer",
|
||||
"loc": "Provence, FR",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/Silversthorn" ], null ],
|
||||
"blurb": "Developer at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"",
|
||||
"pic": "/static/assets/images/credits/silversthorn.png"
|
||||
},
|
||||
{
|
||||
"name": "ThatOneLukas",
|
||||
"title": "Software Engineer",
|
||||
"loc": "Helsinki, FI",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/LukasDoesDev" ], null ],
|
||||
"blurb": "Arch Linux enthusiast who likes 80s/90s music, Rust (The programming language) and light distros like Arch Linux btw. Dislikes C, C++, Go, and Microsoft. Also doesn't like Finnish weather that freezes molten snow to (deadly) ice.",
|
||||
"pic": "/static/assets/images/credits/lukas.png"
|
||||
},
|
||||
{
|
||||
"name": "Zedifus",
|
||||
"title": "DevOps Engineer",
|
||||
"loc": "Scotland, UK",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/Zedifus" ], "DevOps" ],
|
||||
"blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.",
|
||||
"pic": "/static/assets/images/credits/zedifus.jpg"
|
||||
}
|
||||
],
|
||||
"support": [
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"title": "Community Manager",
|
||||
"loc": "South Central, US",
|
||||
"tags": [ "Staff", null, "Wiki" ],
|
||||
"blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.",
|
||||
"pic": "/static/assets/images/credits/isilverfyre.png"
|
||||
},
|
||||
{
|
||||
"name": "Quentin",
|
||||
"title": "Document Curator",
|
||||
"loc": null,
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/qub3d" ], "Wiki" ],
|
||||
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
|
||||
"pic": "/static/assets/images/credits/qub3d.png"
|
||||
},
|
||||
{
|
||||
"name": "DarthLeo",
|
||||
"title": "Support Engineer",
|
||||
"loc": "East Coast, US",
|
||||
"tags": [ "Staff", null, "Discord" ],
|
||||
"blurb": "Just a simple gamer.",
|
||||
"pic": "/static/assets/images/credits/darthLeo.png"
|
||||
}
|
||||
],
|
||||
"retired": [
|
||||
{
|
||||
"name": "Kev Dagoat",
|
||||
"title": "Head of Development",
|
||||
"loc": "East Coast, AU",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/kevdagoat" ], "HOD" ],
|
||||
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.",
|
||||
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
|
||||
},
|
||||
{
|
||||
"name": "Manu",
|
||||
"title": null,
|
||||
"loc": "Eastern, CA",
|
||||
"tags": [ "Staff", "Developer", "Project Manager" ],
|
||||
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
|
||||
"pic": "/static/assets/images/credits/manu.png"
|
||||
},
|
||||
{
|
||||
"name": "UltraBlack",
|
||||
"title": null,
|
||||
"loc": "Bavaria, DE",
|
||||
"tags": [ "Staff", null, "Idea Manager" ],
|
||||
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
|
||||
"pic": "/static/assets/images/credits/ultrablack.png"
|
||||
},
|
||||
{
|
||||
"name": "MC Gaming",
|
||||
"title": null,
|
||||
"loc": "Central, UK",
|
||||
"tags": [ "Staff", [ "Developer", "https://gitlab.com/MCgamin1738" ], null ],
|
||||
"blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.",
|
||||
"pic": "/static/assets/images/credits/mcgaming.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"translations": {
|
||||
"ptarrant": [
|
||||
"Sarcasm",
|
||||
"Wit"
|
||||
],
|
||||
"Lukas": [
|
||||
"Finnish"
|
||||
],
|
||||
"Manu": [
|
||||
"French"
|
||||
],
|
||||
"Silversthorn": [
|
||||
"French"
|
||||
],
|
||||
"UltraBlack": [
|
||||
"German"
|
||||
]
|
||||
"Data Unavailable": ["🌎🪣🔗👎😭"]
|
||||
},
|
||||
"patrons": [
|
||||
{
|
||||
"name": "Richard B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "JOHN C",
|
||||
"level": "Crafty Advocate"
|
||||
},
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Dean R",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "scott m",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "zedifus",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Lino",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "PeterPorker3",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "Ewari",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "Thyodas",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "AngryPanda",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Chris T",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Adam B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Eric G",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Chris B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Emmet d",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Steven T",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Row H",
|
||||
"level": "Crafty Supporter"
|
||||
"name": "Data Unavailable 🌎🪣🔗👎😭",
|
||||
"level": ""
|
||||
}
|
||||
],
|
||||
"lastUpdate": 1637281075498
|
||||
"lastUpdate": 0
|
||||
}
|
||||
|
@ -64,7 +64,7 @@
|
||||
"handlers": ["tornado_access_file_handler"],
|
||||
"propagate": false
|
||||
},
|
||||
"schedule": {
|
||||
"apscheduler": {
|
||||
"level": "INFO",
|
||||
"handlers": ["schedule_file_handler"],
|
||||
"propagate": false
|
||||
|
@ -2,5 +2,5 @@
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": 0,
|
||||
"meta": "alpha.3"
|
||||
"meta": "alpha.3.5"
|
||||
}
|
||||
|
@ -50,7 +50,11 @@
|
||||
}
|
||||
|
||||
.mc-log-error{
|
||||
color:#ff6258;
|
||||
color:#af463f;
|
||||
}
|
||||
|
||||
.mc-log-fatal{
|
||||
color:#da0f00;
|
||||
}
|
||||
|
||||
.mc-log-keyword{
|
||||
|
@ -10449,10 +10449,10 @@ pre {
|
||||
padding-right: 80px; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]):before {
|
||||
border-color: #0b0b0b; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) .menu-title {
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) {
|
||||
color: #0b0b0b; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) .menu-arrow::before {
|
||||
color: #0b0b0b; }
|
||||
.sidebar > .nav:not(.sub-menu) > .nav-item:hover:not(.nav-profile):not(.hover-open) > .nav-link:not([aria-expanded="true"]) .menu-arrow:before {
|
||||
color: #b9c0d3; }
|
||||
|
||||
/* style for off-canvas menu*/
|
||||
@media screen and (max-width: 991px) {
|
||||
|
BIN
app/frontend/static/assets/images/credits/darthLeo.png
Normal file
BIN
app/frontend/static/assets/images/credits/darthLeo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 96c48.6 0 88 39.4 88 88s-39.4 88-88 88-88-39.4-88-88 39.4-88 88-88zm0 344c-58.7 0-111.3-26.6-146.5-68.2 18.8-35.4 55.6-59.8 98.5-59.8 2.4 0 4.8.4 7.1 1.1 13 4.2 26.6 6.9 40.9 6.9 14.3 0 28-2.7 40.9-6.9 2.3-.7 4.7-1.1 7.1-1.1 42.9 0 79.7 24.4 98.5 59.8C359.3 421.4 306.7 448 248 448z" style="fill: #b9c0d3;"/></svg>
|
After Width: | Height: | Size: 594 B |
156
app/frontend/static/assets/images/logo_long.svg
Normal file
156
app/frontend/static/assets/images/logo_long.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 36 KiB |
81
app/frontend/static/assets/images/logo_small.svg
Normal file
81
app/frontend/static/assets/images/logo_small.svg
Normal file
@ -0,0 +1,81 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 614 412">
|
||||
|
||||
<!--<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
class="crafty-svg-bg"
|
||||
fill="hsl(234, 24%, 17%)"
|
||||
/>-->
|
||||
|
||||
<defs>
|
||||
<mask id="mask-rect">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<!--
|
||||
307 - 200 / 2 = 207
|
||||
206 - 200 / 2 = 106
|
||||
-->
|
||||
<rect
|
||||
width="200"
|
||||
height="200"
|
||||
x="207"
|
||||
y="106"
|
||||
fill="black"
|
||||
transform-origin="center"
|
||||
transform="rotate(45)"
|
||||
/>
|
||||
<!-- <circle r="50" cx="307" cy="206" fill="black"/> -->
|
||||
<circle r="55" cx="150" cy="206" fill="black"/>
|
||||
</mask>
|
||||
<mask id="mask-rect2">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<!--
|
||||
307 - 200 / 2 = 207
|
||||
206 - 200 / 2 = 106
|
||||
-->
|
||||
<rect
|
||||
width="200"
|
||||
height="200"
|
||||
x="207"
|
||||
y="106"
|
||||
fill="black"
|
||||
transform-origin="center"
|
||||
transform="rotate(45)"
|
||||
/>
|
||||
<!-- <circle r="50" cx="307" cy="206" fill="black"/> -->
|
||||
<circle r="55" cx="464" cy="206" fill="black"/>
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<!--
|
||||
purple: hsl(266, 71%, 57%)
|
||||
green: hsl(160, 59%, 45%)
|
||||
blue: hsl(192, 99%, 45%)
|
||||
-->
|
||||
<!-- center of image: 307 206 -->
|
||||
<!-- left circle offset: -157 0 -->
|
||||
<!-- right circle offset: 157 0 -->
|
||||
|
||||
<circle r="120" cx="150" cy="206" fill="hsl(160, 59%, 45%)" mask="url(#mask-rect)" />
|
||||
<circle r="120" cx="464" cy="206" fill="hsl(192, 99%, 45%)" mask="url(#mask-rect2)"/>
|
||||
|
||||
<!-- the purple thing in the middle -->
|
||||
<g fill="hsl(266, 71%, 57%)">
|
||||
<circle r="35" cx="150" cy="206"/>
|
||||
<circle r="35" cx="464" cy="206"/>
|
||||
<path
|
||||
d="
|
||||
M 307 206
|
||||
m -157 -32
|
||||
q 157 30 314 0
|
||||
|
||||
l 0 65
|
||||
q -157 -30 -314 0
|
||||
|
||||
Z
|
||||
"
|
||||
/>
|
||||
</g>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -207,6 +207,10 @@ if ($('canvas').length) {
|
||||
body.toggleClass('sidebar-hidden');
|
||||
} else {
|
||||
body.toggleClass('sidebar-icon-only');
|
||||
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
if (vw >= 1200) {
|
||||
localStorage.setItem('crafty-sidebar-expanded', !body.hasClass('sidebar-icon-only'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -231,4 +235,4 @@ if ($('canvas').length) {
|
||||
$(this).not(".brand-logo").attr('toggle-status', 'closed');
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
})(jQuery);
|
||||
|
@ -1,151 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
{% block meta %}{% end %}
|
||||
<title>{% block title %}{{ _('Default') }}{% end %}</title>
|
||||
<html lang="{{ data['lang_page'] }}">
|
||||
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
|
||||
<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/crafty.css">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
{% block meta %}{% end %}
|
||||
<title>{% block title %}{{ _('Default') }}{% end %}</title>
|
||||
|
||||
<!-- endinject -->
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
|
||||
<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/crafty.css">
|
||||
|
||||
<!-- Plugin css for this page -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/jvectormap/jquery-jvectormap.css">
|
||||
<!-- End Plugin css for this page -->
|
||||
<!-- endinject -->
|
||||
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<!-- Plugin css for this page -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/jvectormap/jquery-jvectormap.css">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
|
||||
<!-- End Plugin css for this page -->
|
||||
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
<link rel="stylesheet" href="/static/assets/css/crafty.css">
|
||||
</head>
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
|
||||
<body class="dark-theme">
|
||||
<div class="container-scroller">
|
||||
<!-- partial:partials/_navbar.html -->
|
||||
<nav class="navbar default-layout col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
|
||||
<div class="text-center navbar-brand-wrapper d-flex align-items-top justify-content-center">
|
||||
<a class="navbar-brand brand-logo" href="/panel/dashboard">
|
||||
<img src="/static/assets/images/logo_long.jpg" alt="logo" /> </a>
|
||||
<a class="navbar-brand brand-logo-mini" href="/panel/dashboard">
|
||||
<img src="/static/assets/images/logo_square.jpg" alt="logo" /> </a>
|
||||
</div>
|
||||
<div class="navbar-menu-wrapper d-flex align-items-center">
|
||||
<button class="navbar-toggler navbar-toggler align-self-center" type="button" data-toggle="minimize">
|
||||
<span class="mdi mdi-menu"></span>
|
||||
</button>
|
||||
<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="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<!-- End Alpine.js -->
|
||||
|
||||
</head>
|
||||
|
||||
<body class="dark-theme">
|
||||
<div class="container-scroller">
|
||||
<!-- partial:partials/_navbar.html -->
|
||||
<nav class="navbar default-layout col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
|
||||
<div class="text-center navbar-brand-wrapper d-flex align-items-top justify-content-center">
|
||||
<a class="navbar-brand brand-logo" href="/panel/dashboard">
|
||||
<img src="/static/assets/images/logo_long.svg" alt="logo" /> </a>
|
||||
<a class="navbar-brand brand-logo-mini" href="/panel/dashboard">
|
||||
<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>
|
||||
|
||||
{% include notify.html %}
|
||||
|
||||
<button class="navbar-toggler navbar-toggler-right d-lg-none align-self-center" type="button" data-toggle="offcanvas">
|
||||
<span class="mdi mdi-menu"></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% include main_menu.html %}
|
||||
|
||||
<div class="main-panel">
|
||||
|
||||
<div class="warnings">
|
||||
<noscript class="noscript-warning" style="padding: 20px; background-color: rgb(247, 151, 15);">
|
||||
<div>{% raw translate('base', 'doesNotWorkWithoutJavascript', data['lang']) %}</div>
|
||||
</noscript>
|
||||
</div>
|
||||
|
||||
{% block content %}
|
||||
{% end %}
|
||||
|
||||
{% include footer.html %}
|
||||
|
||||
</div>
|
||||
<!-- main-panel ends -->
|
||||
|
||||
<button class="navbar-toggler navbar-toggler-right d-lg-none align-self-center" type="button"
|
||||
data-toggle="offcanvas">
|
||||
<span class="mdi mdi-menu"></span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- page-body-wrapper ends -->
|
||||
</nav>
|
||||
|
||||
{% include main_menu.html %}
|
||||
|
||||
<div class="main-panel">
|
||||
|
||||
<div class="warnings">
|
||||
<noscript class="noscript-warning" style="padding: 20px; background-color: rgb(247, 151, 15);">
|
||||
<div>{% raw translate('base', 'doesNotWorkWithoutJavascript', data['lang']) %}</div>
|
||||
</noscript>
|
||||
</div>
|
||||
|
||||
{% block content %}
|
||||
{% end %}
|
||||
|
||||
{% include footer.html %}
|
||||
|
||||
</div>
|
||||
<!-- main-panel ends -->
|
||||
|
||||
</div>
|
||||
<!-- page-body-wrapper ends -->
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.notifications {
|
||||
position: fixed;
|
||||
width: 200px;
|
||||
top: 70px;
|
||||
right: 0px;
|
||||
}
|
||||
.notification {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 0.5rem;
|
||||
padding-left: 0.7rem;
|
||||
width: 180px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
background: #282a40;
|
||||
transition: right 0.75s, opacity 0.75s, top 0.75s;
|
||||
right: -6rem;
|
||||
opacity: 0.1;
|
||||
margin-bottom: 1rem;
|
||||
z-index: 999;
|
||||
top: 0px;
|
||||
}
|
||||
.notification.active {
|
||||
right: 0rem;
|
||||
opacity: 1;
|
||||
}
|
||||
.notification.remove {
|
||||
right: 0rem;
|
||||
opacity: 0.1;
|
||||
top: -2rem;
|
||||
}
|
||||
.notification p {
|
||||
margin: 0px;
|
||||
width: calc(160.8px - 16px);
|
||||
z-index: inherit;
|
||||
}
|
||||
.notification span {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.46rem;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
font-size: 22px;
|
||||
user-select: none;
|
||||
z-index: inherit;
|
||||
}
|
||||
</style>
|
||||
<div class="notifications"></div>
|
||||
<style>
|
||||
.notifications {
|
||||
position: fixed;
|
||||
width: 200px;
|
||||
top: 70px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 0.5rem;
|
||||
padding-left: 0.7rem;
|
||||
width: 180px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
background: #282a40;
|
||||
transition: right 0.75s, opacity 0.75s, top 0.75s;
|
||||
right: -6rem;
|
||||
opacity: 0.1;
|
||||
margin-bottom: 1rem;
|
||||
z-index: 999;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.notification.active {
|
||||
right: 0rem;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.notification.remove {
|
||||
right: 0rem;
|
||||
opacity: 0.1;
|
||||
top: -2rem;
|
||||
}
|
||||
|
||||
.notification p {
|
||||
margin: 0px;
|
||||
width: calc(160.8px - 16px);
|
||||
z-index: inherit;
|
||||
}
|
||||
|
||||
.notification span {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.46rem;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
font-size: 22px;
|
||||
user-select: none;
|
||||
z-index: inherit;
|
||||
}
|
||||
</style>
|
||||
<div class="notifications"></div>
|
||||
|
||||
|
||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||
<script src="/static/assets/js/shared/misc.js"></script>
|
||||
<script type="text/javascript" src="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.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
|
||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||
<script src="/static/assets/js/shared/misc.js"></script>
|
||||
<script type="text/javascript" src="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.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
|
||||
<script type="text/javascript" src="/static/assets/js/motd.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
$.extend($.fn.dataTable.defaults, {
|
||||
language: {% raw translate('datatables', 'i18n', data['lang']) %}
|
||||
<script>
|
||||
$.extend($.fn.dataTable.defaults, {
|
||||
language: {% raw translate('datatables', 'i18n', data['lang']) %}
|
||||
})
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
// tool tips
|
||||
@ -154,13 +178,13 @@
|
||||
})
|
||||
|
||||
// Notify
|
||||
$(document).ready(function(){
|
||||
$("#notificationDropdown").click(function(){
|
||||
$.get("/ajax/announcements", function(data){
|
||||
$(document).ready(function () {
|
||||
$("#notificationDropdown").click(function () {
|
||||
$.get("/ajax/announcements", function (data) {
|
||||
console.log(data);
|
||||
bootbox.alert({
|
||||
title: "Notifications",
|
||||
message: data,
|
||||
title: "Notifications",
|
||||
message: data,
|
||||
|
||||
});
|
||||
});
|
||||
@ -168,74 +192,153 @@
|
||||
});
|
||||
|
||||
// {% if request.protocol == 'https' %}
|
||||
let usingWebSockets = true;
|
||||
let usingWebSockets = true;
|
||||
|
||||
let listenEvents = [];
|
||||
let listenEvents = [];
|
||||
|
||||
try {
|
||||
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
|
||||
page = 'page=' + encodeURIComponent(location.pathname)
|
||||
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
|
||||
wsInternal.onopen = function() {
|
||||
console.log('opened WebSocket connection:', wsInternal)
|
||||
};
|
||||
wsInternal.onmessage = function (rawMessage) {
|
||||
var message = JSON.parse(rawMessage.data);
|
||||
try {
|
||||
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
|
||||
page = 'page=' + encodeURIComponent(location.pathname)
|
||||
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
|
||||
wsInternal.onopen = function () {
|
||||
console.log('opened WebSocket connection:', wsInternal)
|
||||
};
|
||||
wsInternal.onmessage = function (rawMessage) {
|
||||
var message = JSON.parse(rawMessage.data);
|
||||
|
||||
console.log('got message: ', message)
|
||||
console.log('got message: ', message)
|
||||
|
||||
listenEvents
|
||||
.filter(listenedEvent => listenedEvent.event == message.event)
|
||||
.forEach(listenedEvent => listenedEvent.callback(message.data))
|
||||
};
|
||||
wsInternal.onerror = function (errorEvent) {
|
||||
console.error('WebSocket Error', errorEvent);
|
||||
};
|
||||
wsInternal.onclose = function (closeEvent) {
|
||||
console.log('Closed WebSocket', closeEvent);
|
||||
};
|
||||
listenEvents
|
||||
.filter(listenedEvent => listenedEvent.event == message.event)
|
||||
.forEach(listenedEvent => listenedEvent.callback(message.data))
|
||||
};
|
||||
wsInternal.onerror = function (errorEvent) {
|
||||
console.error('WebSocket Error', errorEvent);
|
||||
};
|
||||
wsInternal.onclose = function (closeEvent) {
|
||||
console.log('Closed WebSocket', closeEvent);
|
||||
};
|
||||
|
||||
|
||||
webSocket = {
|
||||
on: function (event, callback) {
|
||||
console.log('registered ' + event + ' event');
|
||||
listenEvents.push({ event: event, callback: callback })
|
||||
},
|
||||
emit: function (event, data) {
|
||||
var message = {
|
||||
event: event,
|
||||
data: data
|
||||
}
|
||||
|
||||
wsInternal.send(JSON.stringify(message));
|
||||
webSocket = {
|
||||
on: function (event, callback) {
|
||||
console.log('registered ' + event + ' event');
|
||||
listenEvents.push({ event: event, callback: callback })
|
||||
},
|
||||
emit: function (event, data) {
|
||||
var message = {
|
||||
event: event,
|
||||
data: data
|
||||
}
|
||||
|
||||
wsInternal.send(JSON.stringify(message));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error while making websocket helpers', error);
|
||||
usingWebSockets = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error while making websocket helpers', error);
|
||||
usingWebSockets = false;
|
||||
}
|
||||
// {% else %}
|
||||
let usingWebSockets = false;
|
||||
warn('WebSockets are not supported in Crafty if not using the https protocol')
|
||||
var webSocket;
|
||||
let usingWebSockets = false;
|
||||
warn('WebSockets are not supported in Crafty if not using the https protocol')
|
||||
var webSocket;
|
||||
// {% end%}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_error', function (start_error) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if(x){
|
||||
x.remove()}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if(x){
|
||||
x.remove()}
|
||||
bootbox.alert({
|
||||
message: start_error.error,
|
||||
callback: function () {
|
||||
location.reload();
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_error', function (start_error) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
bootbox.alert({
|
||||
message: start_error.error,
|
||||
callback: function () {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
if (webSocket) {
|
||||
webSocket.on('send_logs_bootbox', function (server_id) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
bootbox.alert({
|
||||
title: "{{ translate('notify', 'downloadLogs', data['lang']) }}",
|
||||
message: "{{ translate('notify', 'finishedPreparing', data['lang']) }}",
|
||||
buttons: {
|
||||
ok: {
|
||||
label: 'Download',
|
||||
className: 'btn-info'
|
||||
}
|
||||
},
|
||||
callback: function () {
|
||||
console.log("in callback")
|
||||
location.href = "/panel/download_support_package";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_eula_bootbox', function (server_id) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
bootbox.confirm({
|
||||
title: '{% raw translate("error", "eulaTitle", data['lang']) %}',
|
||||
message: '{% raw translate("error", "eulaMsg", data['lang']) %} <br><br><a href="https://account.mojang.com/documents/minecraft_eula" target="_blank">EULA</a><br><br>{% raw translate("error", "eulaAgree", data['lang']) %}',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes',
|
||||
className: 'btn-info'
|
||||
},
|
||||
cancel: {
|
||||
label: 'No',
|
||||
className: 'btn-secondary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result == true) {
|
||||
eulaAgree(server_id.id)
|
||||
}
|
||||
else {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function eulaAgree(server_id, command) {
|
||||
//< !--this getCookie function is in base.html-- >
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/eula?id=' + server_id,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function warn(message) {
|
||||
@ -255,7 +358,7 @@ if (webSocket) {
|
||||
closeEl.style.lineHeight = '20px';
|
||||
closeEl.style.cursor = 'pointer';
|
||||
|
||||
closeEl.addEventListener('click', function () {this.parentElement.style.display='none';});
|
||||
closeEl.addEventListener('click', function () { this.parentElement.style.display = 'none'; });
|
||||
|
||||
var parentEl = document.createElement('div');
|
||||
|
||||
@ -276,14 +379,14 @@ if (webSocket) {
|
||||
}
|
||||
|
||||
function notify(message) {
|
||||
console.log(`notify(${message}})`);
|
||||
console.log(`notify(${message})`);
|
||||
var paragraphEl = document.createElement('p');
|
||||
var closeEl = document.createElement('span');
|
||||
|
||||
paragraphEl.textContent = message;
|
||||
|
||||
closeEl.innerHTML = '×';
|
||||
closeEl.addEventListener('click', function () {closeNotification(this)});
|
||||
closeEl.addEventListener('click', function () { closeNotification(this) });
|
||||
|
||||
var parentEl = document.createElement('div');
|
||||
parentEl.appendChild(paragraphEl);
|
||||
@ -311,12 +414,34 @@ if (webSocket) {
|
||||
}
|
||||
webSocket.on('notification', notify);
|
||||
|
||||
</script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
console.log('%c[Crafty Controller] %cAlpine.js pre-initialization!', 'font-weight: 900; color: #800080;', 'color: #eee;');
|
||||
})
|
||||
document.addEventListener('alpine:initialized', () => {
|
||||
console.log('%c[Crafty Controller] %cAlpine.js initialized!', 'font-weight: 900; color: #800080;', 'color: #eee;');
|
||||
})
|
||||
|
||||
{% block js %}
|
||||
<!-- Custom js for this page -->
|
||||
<!-- End custom js for this page -->
|
||||
{% end %}
|
||||
$(document).ready(function () {
|
||||
console.log('%c[Crafty Controller] %cReady for JS!', 'font-weight: 900; color: #800080;', 'font-weight: 900; color: #eee;');
|
||||
$('#support_logs').click(function () {
|
||||
var dialog = bootbox.dialog({
|
||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i>{{ translate('notify', 'preparingLogs', data['lang']) }}</p>',
|
||||
closeButton: false
|
||||
});
|
||||
setTimeout(function () {
|
||||
location.href = "/panel/support_logs";
|
||||
}, 6000);
|
||||
|
||||
</body>
|
||||
</html>
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% block js %}
|
||||
<!-- Custom js for base.html page partial pages -->
|
||||
<!-- End custom js for base.html page -->
|
||||
{% end %}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -19,7 +19,8 @@
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
<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">
|
||||
<div class="container-scroller">
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Blank Page{% end %}
|
||||
|
@ -2,10 +2,11 @@
|
||||
<footer class="footer">
|
||||
<div class="container-fluid ">
|
||||
|
||||
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
|
||||
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}
|
||||
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 - <span x-data x-text="new Date().getFullYear()"></span> <a href="https://craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
|
||||
|
||||
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
<!-- partial -->
|
||||
<!-- partial -->
|
||||
|
@ -1,6 +1,68 @@
|
||||
<!-- partial -->
|
||||
<div class="container-fluid page-body-wrapper">
|
||||
<!-- 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;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
};
|
||||
function isExtraLargeBreakpoint() {
|
||||
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
return vw >= 1200;
|
||||
}
|
||||
function isLargeBreakpoint() {
|
||||
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
return vw >= 992;
|
||||
}
|
||||
$(document).ready(function() {
|
||||
sidebarResizeHandler(null);
|
||||
$(window).on(
|
||||
'resize',
|
||||
debounce(sidebarResizeHandler, 25, true)
|
||||
);
|
||||
});
|
||||
function sidebarResizeHandler(e) {
|
||||
/*
|
||||
Viewport sizes: Extra large (vw >= 1200px), large (vw >= 992px), medium (vw >= 768px)
|
||||
- A localstorage item is set to remember a user's preference between collapsed or expanded.
|
||||
- For extra large viewports, the sidebar is the user's preference (by default expanded). When
|
||||
expanded or collapsed manually, it doesn't overlap the page content and the preference
|
||||
gets saved to a localstorage item.
|
||||
- For large viewports, the sidebar is collapsed. When expanded manually, it doesn't overlap
|
||||
the page content. The user's localstorage preference is not overridden during this state.
|
||||
- For medium and below viewports, the sidebar is hidden behing a hamburger icon. When expanded, the sidebar
|
||||
overlaps the page content. The user's localstorage preference is not overridden during this state.
|
||||
|
||||
More code in `app/frontend/static/assets/js/shared/misc.js` and `app/frontend/templates/base.html`
|
||||
*/
|
||||
if (isExtraLargeBreakpoint()) {
|
||||
let value = localStorage.getItem('crafty-sidebar-expanded') !== 'false';
|
||||
$('body').toggleClass('sidebar-icon-only', !value);
|
||||
localStorage.setItem('crafty-sidebar-expanded', value);
|
||||
} else if (isLargeBreakpoint()) {
|
||||
$('body').toggleClass('sidebar-icon-only', true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<nav class="sidebar sidebar-offcanvas" id="sidebar">
|
||||
<ul class="nav">
|
||||
|
||||
@ -37,7 +99,7 @@
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://gitlab.com/crafty-controller/crafty-web/-/wikis/home" target="_blank">
|
||||
<a class="nav-link" href="https://wiki.craftycontrol.com" target="_blank">
|
||||
<i class="fas fa-book"></i>
|
||||
<span class="menu-title">{{ translate('sidebar', 'documentation', data['lang']) }}</span>
|
||||
</a>
|
||||
@ -68,4 +130,4 @@
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
<!-- partial -->
|
||||
<!-- partial -->
|
||||
|
@ -18,20 +18,25 @@
|
||||
|
||||
<li class="nav-item dropdown user-dropdown">
|
||||
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
|
||||
<img class="img-xs rounded-circle" src="/static/assets/images/faces-clipart/pic-1.png" alt="Profile image"> </a>
|
||||
<img class="img-xs rounded-circle profile-picture" src="{{ data['user_image'] }}" alt="Profile image"> </a>
|
||||
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
|
||||
<div class="dropdown-header text-center">
|
||||
<img class="img-md rounded-circle" src="/static/assets/images/faces-clipart/pic-1.png" alt="Profile image">
|
||||
<img class="img-md rounded-circle profile-picture" src="{{ data['user_image'] }}" alt="Profile image">
|
||||
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
|
||||
<p class="font-weight-light text-muted mb-0">Roles: </p>
|
||||
{% for r in data['user_role'] %}
|
||||
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
|
||||
{% end %}
|
||||
{% if data.get('api_key') %}
|
||||
<p class="mt-3">Logged in as API key "{{ data['api_key']['name'] }}"</p>
|
||||
{% end %}
|
||||
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
|
||||
</div>
|
||||
{% if "Super User" in data['user_role'] %}
|
||||
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i> Activity</a>
|
||||
<a class="dropdown-item" id="support_logs" ><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}</i></a>
|
||||
{% if data['superuser'] %}
|
||||
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i>{{ translate('notify', 'activityLog', data['lang']) }}</a>
|
||||
{% end %}
|
||||
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>Sign Out</a>
|
||||
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{ translate('notify', 'logout', data['lang']) }}</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
@ -1,8 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Activity Logs{% end %}
|
||||
@ -21,40 +19,51 @@
|
||||
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 grid-margin">
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<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-history"></i> Audit Logs</h4>
|
||||
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table" id="audit_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td>Time</td>
|
||||
<td>Action</td>
|
||||
<td>Server ID</td>
|
||||
<td>IP</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data['audit_logs'] %}
|
||||
<tr>
|
||||
<td>{{ row['user_name'] }}</td>
|
||||
<td>
|
||||
{{ row['created'].strftime('%m-%d-%Y %H:%M:%S') }}
|
||||
</td>
|
||||
<td>{{ row['log_msg'] }}</td>
|
||||
<td>{{ row['server_id'] }}</td>
|
||||
<td>{{ row['source_ip'] }}</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<td>Username</td>
|
||||
<td>Time</td>
|
||||
<td>Action</td>
|
||||
<td>Server ID</td>
|
||||
<td>IP</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data['audit_logs'] %}
|
||||
<tr>
|
||||
<td>{{ row['user_name'] }}</td>
|
||||
<td>
|
||||
{{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }}
|
||||
</td>
|
||||
<td>{{ row['log_msg'] }}</td>
|
||||
<td>{{ row['server_id'] }}</td>
|
||||
<td>{{ row['source_ip'] }}</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.popover-body{
|
||||
color: white !important;;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@ -69,8 +78,35 @@
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
$('#audit_table').DataTable();
|
||||
$('#audit_table').DataTable({
|
||||
'order': [1, 'desc']
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
|
||||
});
|
||||
$(window).ready(function(){
|
||||
$('body').click(function(){
|
||||
$('.too_small').popover("hide");
|
||||
});
|
||||
});
|
||||
$(window).resize(function() {
|
||||
// This will execute whenever the window is resized
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
else{
|
||||
$('.too_small').popover("hide");
|
||||
} // New width
|
||||
});
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Contribute{% end %}
|
||||
@ -23,7 +22,7 @@
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-3 grid-margin">
|
||||
<div class="col-md-4 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
@ -31,8 +30,8 @@
|
||||
|
||||
<div class="media-body">
|
||||
<p class="card-text">
|
||||
Patrons get access to several perks, such as behind the scenes videos, posts, and updates.
|
||||
Patrons also get access to a Crafty Controller PE (Patreon Edition) with additional functions not in other versions of Crafty.
|
||||
Patrons get access to several perks, such as behind the scenes videos, posts, and updates.<br>
|
||||
Patrons also get early access to new software!
|
||||
</p>
|
||||
<br />
|
||||
<div class="text-center">
|
||||
@ -44,35 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="card-title">One Time Support</h4>
|
||||
|
||||
<div class="media-body">
|
||||
<p class="card-text">
|
||||
Contribute via Paypal, you can contribute any amount, as often or as little as you want.
|
||||
Please understand that while PayPal calls this a "Donation"; this is not a charitable donation and can not be claimed
|
||||
as a charitable donation for tax purposes
|
||||
</p>
|
||||
<br />
|
||||
<div class="text-center">
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_donations" />
|
||||
<input type="hidden" name="business" value="H2HNTLFZAJRXG" />
|
||||
<input type="hidden" name="currency_code" value="USD" />
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
|
||||
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 grid-margin">
|
||||
<div class="col-md-4 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
@ -93,6 +64,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="card-title">More Ways Coming Soon...</h4>
|
||||
|
||||
<div class="media-body">
|
||||
<p class="card-text">
|
||||
Thank you for your interest in contributing to Aracdia Technology's Crafty Controller.
|
||||
We are always thinking of new ways for our community to contribute to this awesome project. <br><br> If you don't see
|
||||
a contribution method that peaks your interest now please check back soon.
|
||||
</p>
|
||||
<br />
|
||||
<div class="text-center">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -1,21 +1,20 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Credits{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('credits', 'pageTitle', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">Credits
|
||||
<small>Without these people, you wouldn't have Crafty</small>
|
||||
<h4 class="page-title">{{ translate('credits', 'pageTitle', data['lang']) }}
|
||||
<small>{{ translate('credits', 'pageDescription', data['lang']) }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -23,303 +22,326 @@
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="far fa-code"></i> Development Team</h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for person in data['staff']['development'] %}
|
||||
<div class="col-md-6 mb-5">
|
||||
<div class="card rounded shadow-none">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
|
||||
<div class="user-avatar mb-auto">
|
||||
<img src="{{ person['pic'] }}"
|
||||
alt="profile image" class="profile-img img-lg rounded-circle">
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="wrapper d-flex align-items-center">
|
||||
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex 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>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="wrapper d-flex align-items-start">
|
||||
{% if person['tags'][0] %}
|
||||
<span class="btn btn-sm btn-info mr-2">{{ 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>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ 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">{{ person['tags'][2][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
</div> <!-- end of user row -->
|
||||
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fa fa-book"></i> Support and Documentation Team</h4>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% for person in data['staff']['support'] %}
|
||||
<div class="col-md-6 mb-5">
|
||||
<div class="card rounded shadow-none">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
|
||||
<div class="user-avatar mb-auto">
|
||||
<img src="{{ person['pic'] }}"
|
||||
alt="profile image" class="profile-img img-lg rounded-circle">
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="wrapper d-flex align-items-center">
|
||||
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex 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>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="wrapper d-flex align-items-start">
|
||||
{% if person['tags'][0] %}
|
||||
<span class="btn btn-sm btn-info mr-2">{{ 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>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ 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">{{ person['tags'][2][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
</div> <!-- end user row-->
|
||||
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="far fa-server"></i> Retired Staff</h4>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
{% for person in data['staff']['retired'] %}
|
||||
<div class="col-md-6 mb-5">
|
||||
<div class="card rounded shadow-none">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
|
||||
<div class="user-avatar mb-auto">
|
||||
<img src="{{ person['pic'] }}"
|
||||
alt="profile image" class="profile-img img-lg rounded-circle">
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="wrapper d-flex align-items-center">
|
||||
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex 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>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="wrapper d-flex align-items-start">
|
||||
{% if person['tags'][0] %}
|
||||
<span class="btn btn-sm btn-info mr-2">{{ 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>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ 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">{{ person['tags'][2][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
</div> <!-- end user row-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="far fa-code"></i> {{ translate('credits', 'developmentTeam', data['lang']) }}</h4>
|
||||
</div>
|
||||
</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="row">
|
||||
<div class="col-md-4" style="max-width: fit-content;">
|
||||
|
||||
<div class="col-lg-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Patreon Supporters</h4>
|
||||
<p class="card-description"> A huge <code>thank you</code> to our Patreon supporters! | <span style="color: #9365B8">Last Update: {{ data["lastUpdate"] }}</span></p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Level</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pat in data["patrons"] %}
|
||||
<tr>
|
||||
<td>{{ pat["name"] }}</td>
|
||||
<td>
|
||||
{% if pat["level"] == "Crafty Sustainer" %}
|
||||
<span class="btn btn-sm btn-info mr-2">Sustainer</span>
|
||||
{% elif pat["level"] == "Crafty Advocate" %}
|
||||
<span class="btn btn-sm btn-primary mr-2">Advocate</span>
|
||||
{% elif pat["level"] == "Crafty Supporter" %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">Supporter</span>
|
||||
<div class="user-avatar mb-auto">
|
||||
{% if person['pic'] %}
|
||||
<img src="{{ person['pic'] }}" alt="profile image" class="profile-img img-lg rounded-circle">
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-secondary mr-2">Other</span>
|
||||
<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">
|
||||
</div>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<div class="wrapper d-flex align-items-center">
|
||||
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Language Translation</h4>
|
||||
<p class="card-description"> A huge <code>thank you</code> to our community who translate! </p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in data['translations'] %}
|
||||
<tr>
|
||||
<td>{{ person }}</td>
|
||||
<td>
|
||||
{% for language in data['translations'][person] %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ language }}</span>
|
||||
<div class="wrapper d-flex 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>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="wrapper d-flex align-items-start">
|
||||
{% if person['tags'][0] %}
|
||||
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% 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>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ 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">{{ person['tags'][2][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div> <!-- end of user row -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
<br />
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
<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> {{ 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">
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
<div class="row">
|
||||
<div class="col-md-4" style="max-width: fit-content;">
|
||||
|
||||
});
|
||||
</script>
|
||||
<div class="user-avatar mb-auto">
|
||||
{% 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">
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
{% end %}
|
||||
<div class="wrapper">
|
||||
<div class="wrapper d-flex align-items-center">
|
||||
<h4 class="mb-0 font-weight-medium">{{ person['name'] }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex 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>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="wrapper d-flex align-items-start">
|
||||
{% if person['tags'][0] %}
|
||||
<span class="btn btn-sm btn-info mr-2">{{ 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>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ 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">{{ person['tags'][2][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
<h5><strong>{{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
</div> <!-- end 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="far fa-server"></i> {{ 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">
|
||||
{% 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">
|
||||
</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>
|
||||
|
||||
<div class="wrapper d-flex 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>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="wrapper d-flex align-items-start">
|
||||
{% if person['tags'][0] %}
|
||||
<span class="btn btn-sm btn-info mr-2">{{ 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>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ 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">{{ person['tags'][2][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][2] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
<h5><strong>{{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div> <!-- end user row-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fab fa-patreon"></i> {{ translate('credits', 'patreonSupporter',
|
||||
data['lang'])
|
||||
}}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-description"> {{ translate('credits', 'hugeDesc', data['lang']) }}
|
||||
<code>{{ translate('credits', 'thankYou', data['lang']) }}</code> {{ translate('credits', 'patreonDesc', data['lang']) }} | <span style="color: #9365B8">{{ translate('credits', 'patreonUpdate', data['lang']) }} {{ data["lastUpdate"] }}</span>
|
||||
</p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ translate('credits', 'patreonName', data['lang']) }}</th>
|
||||
<th>{{ translate('credits', 'patreonLevel', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pat in data["patrons"] %}
|
||||
<tr>
|
||||
<td>{{ pat["name"] }}</td>
|
||||
<td>
|
||||
{% if pat["level"] == "Crafty Sustainer" %}
|
||||
<span class="btn btn-sm btn-info mr-2">Sustainer</span>
|
||||
{% elif pat["level"] == "Crafty Advocate" %}
|
||||
<span class="btn btn-sm btn-primary mr-2">Advocate</span>
|
||||
{% elif pat["level"] == "Crafty Supporter" %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">Supporter</span>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-secondary mr-2">{{ translate('credits', 'patreonOther', data['lang']) }}</span>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="far fa-language"></i> {{ translate('credits', 'translationTitle', data['lang']) }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text"> {{ translate('credits', 'hugeDesc', data['lang']) }}
|
||||
<code>{{ translate('credits', 'thankYou', data['lang']) }}</code> {{ translate('credits', 'translationDesc', data['lang']) }}
|
||||
</p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ translate('credits', 'translationName', data['lang']) }}</th>
|
||||
<th>{{ translate('credits', 'translator', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in data['translations'] %}
|
||||
<tr>
|
||||
<td>{{ person }}</td>
|
||||
<td class="pb-0">
|
||||
<div class="row">
|
||||
{% for language in data['translations'][person] %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2" style="margin-bottom: 12px;">{{ language }}</span>
|
||||
{% end %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log('ready for JS!')
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<meta http-equiv="refresh" content="60">
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard', data['lang']) }}{% end %}
|
||||
@ -10,11 +9,14 @@
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">{{ translate('dashboard', 'dashboard', data['lang']) }}</h4>
|
||||
<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></h4>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -29,16 +31,21 @@
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}</h5>
|
||||
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}
|
||||
</h5>
|
||||
<h3 class="mb-0 font-weight-semibold"> <i class="fas fa-chart-line"></i></h3>
|
||||
|
||||
</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') }}" >
|
||||
{{ translate('dashboard', 'cpuUsage', data['lang']) }}: <span id="cpu_usage">{{ data.get('hosts_data').get('cpu_usage') }}</span>
|
||||
<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') }}" >
|
||||
{{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{ data.get('hosts_data').get('mem_percent') }}%</span>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -46,26 +53,28 @@
|
||||
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}</h5>
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}
|
||||
</h5>
|
||||
<h3 class="mb-0 font-weight-semibold">{{ data['server_stats']['total'] }}</h3>
|
||||
|
||||
</div>
|
||||
<div class="wrapper my-auto ml-auto ml-lg-4">
|
||||
<p class="mb-0 text-success">{{ data['server_stats']['running'] }} {{ translate('dashboard', 'online', data['lang']).lower() }}</p>
|
||||
<p class="mb-0 text-warning"> {{ data['server_stats']['stopped'] }} {{ translate('dashboard', 'offline', data['lang']).lower() }}</p>
|
||||
<p class="mb-0 text-success">{{ data['server_stats']['running'] }} {{ translate('dashboard', 'online',
|
||||
data['lang']).lower() }}</p>
|
||||
<p class="mb-0 text-warning"> {{ data['server_stats']['stopped'] }} {{ translate('dashboard',
|
||||
'offline', data['lang']).lower() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}</h5>
|
||||
<h3 class="mb-0 font-weight-semibold">{{ data['num_players'] }}</h3>
|
||||
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}
|
||||
</h5>
|
||||
<h3 class="mb-0 font-weight-semibold" id="total_players">{{ data['num_players'] }}</h3>
|
||||
</div>
|
||||
<div class="wrapper my-auto ml-auto ml-lg-4">
|
||||
<p class="mb-0 text-success">35 {{ translate('dashboard', 'max', data['lang']) }}</p>
|
||||
<p class="mb-0 text-warning">10 {{ translate('dashboard', 'avg', data['lang']) }}</p>
|
||||
<p class="mb-0 text-warning"><span id="max_players">0</span> {{ translate('dashboard', 'max', data['lang']) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -79,24 +88,29 @@
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<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-server"></i> {{ translate('dashboard', 'allServers', data['lang']) }}</h4>
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('dashboard', 'allServers',
|
||||
data['lang']) }}</h4>
|
||||
{% if len(data['servers']) > 0 %}
|
||||
<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><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{ translate('dashboard', 'newServer', data['lang']) }}</a></div>
|
||||
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{
|
||||
translate('dashboard', 'newServer', data['lang']) }}</a></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="table-responsive">
|
||||
{% if len(data['servers']) == 0%}
|
||||
<div style="text-align: center; color: grey;">
|
||||
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
|
||||
<br>
|
||||
<h7>{{ translate('dashboard', 'no-servers', data['lang']) }} {{ translate('dashboard', 'newServer', data['lang']) }}.</h7>
|
||||
</div>
|
||||
{% if len(data['servers']) == 0%}
|
||||
<div style="text-align: center; color: grey;">
|
||||
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
|
||||
<br>
|
||||
<h7>{{ translate('dashboard', 'no-servers', data['lang']) }} {{ translate('dashboard', 'newServer',
|
||||
data['lang']) }}.</h7>
|
||||
</div>
|
||||
|
||||
{% end %}
|
||||
{% if len(data['servers']) > 0 %}
|
||||
{% end %}
|
||||
{% if len(data['servers']) > 0 %}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
@ -104,41 +118,71 @@
|
||||
<th>{{ translate('dashboard', 'actions', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'memUsage', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'world', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'size', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'players', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'status', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for server in data['servers'] %}
|
||||
<tr>
|
||||
<td>
|
||||
<tr id="{{server['server_data']['server_id']}}" draggable="true" ondragstart="start()" ondragover="dragover()" ondragend="dragend()">
|
||||
<td draggable="false">
|
||||
<i class="fas fa-server"></i>
|
||||
<a href="/panel/server_detail?id={{server['server_data']['server_id']}}">
|
||||
<a draggable="false" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
|
||||
{{ server['server_data']['server_name'] }}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
|
||||
{% if server['user_command_permission'] %}
|
||||
{% if server['stats']['running'] %}
|
||||
<a class="stop_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'stop', data['lang']) }}> <i class="fas fa-stop"></i></a>
|
||||
<a class="restart_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'restart', data['lang']) }}> <i class="fas fa-sync"></i></a>
|
||||
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title={{ translate('dashboard', 'kill', data['lang']) }}> <i class="fas fa-skull"></i></a>
|
||||
{% elif server['stats']['updating']%}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating', data['lang']) }}</i></a>
|
||||
{% elif server['stats']['waiting_start']%}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="" title={{ translate('dashboard', 'delay-explained', data['lang'])}}>{{ translate('dashboard', 'starting', data['lang']) }}</i></a>
|
||||
{% else %}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play" data-toggle="tooltip" title={{ translate('dashboard', 'start', data['lang']) }}></i></a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone" data-toggle="tooltip" title={{ translate('dashboard', 'clone', data['lang']) }}></i></a>
|
||||
<a class="kill_button" 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>
|
||||
{% end %}
|
||||
{% if server['stats']['running'] %}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="stop_button"
|
||||
data-toggle="tooltip" title="{{ translate('dashboard', 'stop' , data['lang']) }}">
|
||||
<i class="fas fa-stop"></i>
|
||||
</a>
|
||||
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="restart_button"
|
||||
data-toggle="tooltip" title="{{ translate('dashboard', 'restart' , data['lang']) }}">
|
||||
<i class="fas fa-sync"></i>
|
||||
</a>
|
||||
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="kill_button"
|
||||
data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
|
||||
<i class="fas fa-skull"></i>
|
||||
</a>
|
||||
|
||||
{% elif server['stats']['updating']%}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating',
|
||||
data['lang']) }}</i></a>
|
||||
{% elif server['stats']['waiting_start']%}
|
||||
<!-- WHAT HAPPENED HERE -->
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="" title="{{
|
||||
translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting',
|
||||
data['lang']) }}</i></a>
|
||||
{% elif server['stats']['downloading']%}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'downloading',
|
||||
data['lang']) }}</a>
|
||||
{% else %}
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['cpu']}}">
|
||||
<td 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-bar
|
||||
{% if server['stats']['cpu'] <= 33 %}
|
||||
bg-success
|
||||
@ -147,13 +191,15 @@
|
||||
{% 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="0"
|
||||
aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
{{server['stats']['cpu']}}%
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['mem']}}">
|
||||
<td 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-bar
|
||||
{% if server['stats']['mem_percent'] <= 33 %}
|
||||
bg-success
|
||||
@ -162,40 +208,48 @@
|
||||
{% 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="0"
|
||||
aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
{{server['stats']['mem_percent']}}% -
|
||||
|
||||
{% if server['stats']['mem'] == 0 %}
|
||||
0 MB
|
||||
0 MB
|
||||
{% else %}
|
||||
{{server['stats']['mem']}}
|
||||
{{server['stats']['mem']}}
|
||||
{% end %}
|
||||
</td>
|
||||
<td>
|
||||
{{ server['stats']['world_name'] }} : {{ server['stats']['world_size'] }}
|
||||
<td id="server_world_{{server['server_data']['server_id']}}">
|
||||
{{ server['stats']['world_size'] }}
|
||||
</td>
|
||||
<td>
|
||||
<td 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 />
|
||||
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
|
||||
data['lang']) }} <br />
|
||||
|
||||
{% if server['stats']['desc'] != 'False' %}
|
||||
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{ server['stats']['desc'] }}</span> <br />
|
||||
{% end %}
|
||||
{% if server['stats']['desc'] != 'False' %}
|
||||
{{ server['stats']['desc'] }} <br />
|
||||
{% end %}
|
||||
|
||||
{% if server['stats']['version'] != 'False' %}
|
||||
{{ server['stats']['version'] }}
|
||||
{% end %}
|
||||
{% if server['stats']['version'] != 'False' %}
|
||||
{{ server['stats']['version'] }}
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<td id="server_running_status_{{server['server_data']['server_id']}}">
|
||||
{% if server['stats']['running'] %}
|
||||
<i class="fas fa-thumbs-up"></i> <span class="text-success">{{ translate('dashboard', 'online', data['lang']) }}</span>
|
||||
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
|
||||
data['lang']) }}</span>
|
||||
{% elif server['stats']['crashed'] %}
|
||||
<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed',
|
||||
data['lang']) }}</span>
|
||||
{% else %}
|
||||
<i class="fas fa-thumbs-down"></i> <span class="text-danger">{{ translate('dashboard', 'offline', data['lang']) }}</span>
|
||||
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
|
||||
data['lang']) }}</span>
|
||||
{% end %}
|
||||
</td>
|
||||
<span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}" data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"></span>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
||||
@ -212,197 +266,375 @@
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
<style>
|
||||
.popover-body{
|
||||
color: white !important;;
|
||||
}
|
||||
.popover-body {
|
||||
color: white !important;
|
||||
;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
function display_motd() {
|
||||
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
|
||||
for (element of all_motds) {
|
||||
initParser(element.id, element.id);
|
||||
};
|
||||
}
|
||||
|
||||
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
|
||||
for (element of all_motds) {
|
||||
initParser(element.id, element.id);
|
||||
};
|
||||
});
|
||||
$(window).ready(function(){
|
||||
$('body').click(function(){
|
||||
$('.too_small').popover("hide");
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if ($(window).width() < 1000) {
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
|
||||
});
|
||||
$(window).ready(function () {
|
||||
$('body').click(function () {
|
||||
$('.too_small').popover("hide");
|
||||
});
|
||||
});
|
||||
$(window).resize(function() {
|
||||
// This will execute whenever the window is resized
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
else{
|
||||
$('.too_small').popover("hide");
|
||||
} // New width
|
||||
});
|
||||
});
|
||||
$(window).resize(function () {
|
||||
// This will execute whenever the window is resized
|
||||
if ($(window).width() < 1000) {
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
else {
|
||||
$('.too_small').popover("hide");
|
||||
} // New width
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
|
||||
function send_command (server_id, command){
|
||||
/* this getCookie function is in base.html */
|
||||
var token = getCookie("_xsrf");
|
||||
function send_command(server_id, command) {
|
||||
/* this getCookie function is in base.html */
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){
|
||||
if (command != 'start_server'){
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
/*setTimeout(function () {
|
||||
if (command != 'start_server') {
|
||||
location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
}, 10000);*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function send_kill(server_id) {
|
||||
/* this getCookie function is in base.html */
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/kill?id=' + server_id,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
/*setTimeout(function () {
|
||||
location.reload();
|
||||
}, 10000);*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function update_one_server_status(server) {
|
||||
server_cpu = document.getElementById('server_cpu_' + server.id);
|
||||
server_mem = document.getElementById('server_mem_' + server.id);
|
||||
server_world = document.getElementById('server_world_' + server.id);
|
||||
server_desc = document.getElementById('server_desc_' + server.id);
|
||||
server_online_status = document.getElementById('server_running_status_' + server.id);
|
||||
server_players = document.getElementById('server_players_' + server.id);
|
||||
total_players = document.getElementById('total_players');
|
||||
|
||||
console.log("Received Data : " + server.id + ": " + server);
|
||||
/* TODO Update each element */
|
||||
|
||||
/* Update CPU */
|
||||
cpu_status = "";
|
||||
if (server.cpu <= 33)
|
||||
{
|
||||
cpu_status = "bg-success";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function send_kill (server_id){
|
||||
/* this getCookie function is in base.html */
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/kill?id=' + server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){
|
||||
location.reload();
|
||||
}, 10000);
|
||||
|
||||
else if (server.cpu > 33 && server.cpu <= 66)
|
||||
{
|
||||
cpu_status = "bg-warning";
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
cpu_status = "bg-danger";
|
||||
}
|
||||
server_cpu.innerHTML = `<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="`+ server.cpu +`"><div class="progress-bar `+ cpu_status + `" role="progressbar" style="width: `+ server.cpu + `%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div>`+ server.cpu +`%`;
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
|
||||
$( ".play_button" ).click(function() {
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'start_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStart", data['lang']) %} </div>'
|
||||
/* Update Memory */
|
||||
mem_status = "";
|
||||
total_mem = "";
|
||||
if (server.mem_percent <= 33)
|
||||
{
|
||||
mem_status = "bg-success";
|
||||
} else if (server.mem_percent > 33 && server.mem_percent <= 66)
|
||||
{
|
||||
mem_status = "bg-warning";
|
||||
}
|
||||
else
|
||||
{
|
||||
mem_status = "bg-danger";
|
||||
}
|
||||
|
||||
if (server.mem == 0)
|
||||
{
|
||||
total_mem = "0 MB";
|
||||
}
|
||||
else
|
||||
{
|
||||
total_mem = server.mem;
|
||||
}
|
||||
|
||||
server_mem.innerHTML = `<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="`+ server_mem +`"><div class="progress-bar `+ mem_status + `" role="progressbar" style="width: `+ server.mem_percent + `%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div>`+ server.mem_percent +`% - ` + total_mem;
|
||||
|
||||
/* Update World Infos */
|
||||
server_world.innerHTML = server.world_size
|
||||
|
||||
/* Update Server Infos */
|
||||
if (server.int_ping_results) {
|
||||
/* Update Players */
|
||||
if (server.players) {
|
||||
server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
|
||||
|
||||
server_players.setAttribute('data-players', server.online);
|
||||
server_players.setAttribute('data-max', server.max);
|
||||
let servers = document.getElementsByClassName("server-player-totals");
|
||||
let all_total_players = 0;
|
||||
let all_total_max_players = 0;
|
||||
for(var i = 0; i < servers.length; i++){
|
||||
try{
|
||||
all_total_players += parseInt(servers[i].getAttribute('data-players'));
|
||||
all_total_max_players += parseInt(servers[i].getAttribute('data-max'));
|
||||
}catch{
|
||||
console.log("Player totals are not of type int");
|
||||
}
|
||||
}
|
||||
total_players.innerHTML = all_total_players;
|
||||
document.getElementById('max_players').innerHTML = all_total_max_players;
|
||||
document.getElementById('sync').innerHTML = '';
|
||||
|
||||
|
||||
server_infos = "";
|
||||
server_infos = server.online + " / " + server.max + " {{ translate('dashboard', 'max', data['lang']) }}<br />"
|
||||
}
|
||||
|
||||
/* Update Motd */
|
||||
var motd = "";
|
||||
if (server.desc) {
|
||||
motd = `<span id="input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span>`;
|
||||
server_infos = server_infos + motd + "<br />";
|
||||
}
|
||||
|
||||
/* Version */
|
||||
if (server.version) {
|
||||
server_infos = server_infos + server.version
|
||||
}
|
||||
server_desc.innerHTML = server_infos;
|
||||
}
|
||||
|
||||
/* Update Online Status */
|
||||
var online_status = "";
|
||||
if (server.running) {
|
||||
online_status = `<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])}}</span>`;
|
||||
}
|
||||
else {
|
||||
if (server.crashed){
|
||||
online_status = `<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang'])}}</span>`
|
||||
}else{
|
||||
online_status = `<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang'])}}</span>`;
|
||||
}
|
||||
}
|
||||
server_online_status.innerHTML = online_status;
|
||||
}
|
||||
|
||||
function update_servers_status(data) {
|
||||
update_one_server_status(data[0]);
|
||||
display_motd();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log('ready for JS!')
|
||||
|
||||
$(".play_button").click(function () {
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'start_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStart", data["lang"]) %} </div>'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$( ".stop_button" ).click(function() {
|
||||
console.log("stopping server");
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'stop_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStop", data['lang']) %} </div>'
|
||||
$(".stop_button").click(function () {
|
||||
console.log("stopping server");
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'stop_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStop", data["lang"]) %} </div>'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$( ".restart_button" ).click(function() {
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'restart_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientRestart", data['lang']) %} </div>'
|
||||
$(".restart_button").click(function () {
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'restart_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientRestart", data["lang"]) %} </div>'
|
||||
});
|
||||
});
|
||||
});
|
||||
$( ".kill_button" ).click(function() {
|
||||
$(".kill_button").click(function () {
|
||||
server_id = $(this).attr("data-id");
|
||||
bootbox.confirm({
|
||||
message: "This will kill the server process and all it's subprocesses. Killing a process can potentially corrupt files. Only do this in extreme circumstances. Are you sure you would like to continue?",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: '{% raw translate("dashboard", "kill", data['lang']) %}',
|
||||
message: "This will kill the server process and all it's subprocesses. Killing a process can potentially corrupt files. Only do this in extreme circumstances. Are you sure you would like to continue?",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: '{% raw translate("dashboard", "kill", data["lang"]) %}',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: '{% raw translate("panelConfig", "cancel", data['lang']) %}',
|
||||
},
|
||||
cancel: {
|
||||
label: '{% raw translate("panelConfig", "cancel", data["lang"]) %}',
|
||||
className: 'btn-secondary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
send_kill(server_id);
|
||||
var dialog = bootbox.dialog({
|
||||
title: '{% raw translate("dashboard", "killing", data["lang"]) %}',
|
||||
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
|
||||
});
|
||||
|
||||
dialog.init(function () {
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 15000);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if(result){
|
||||
send_kill(server_id);
|
||||
var dialog = bootbox.dialog({
|
||||
title: '{% raw translate("dashboard", "killing", data['lang']) %}',
|
||||
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
|
||||
});
|
||||
|
||||
dialog.init(function(){
|
||||
setTimeout(function(){
|
||||
location.reload();
|
||||
}, 15000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (webSocket) {
|
||||
cpu_data = document.getElementById('cpu_data');
|
||||
cpu_usage = document.getElementById('cpu_usage');
|
||||
mem_usage = document.getElementById('mem_usage');
|
||||
mem_percent = document.getElementById('mem_percent');
|
||||
|
||||
webSocket.on('update_host_stats', function (hostStats) {
|
||||
var cpuDataTitle = `{% raw translate('dashboard', 'cpuCores', data['lang']) %}: ${hostStats.cpu_cores} <br /> {% raw translate("dashboard", "cpuCurFreq", data['lang']) %}: ${hostStats.cpu_cur_freq} <br /> {% raw translate("dashboard", "cpuMaxFreq", data['lang']) %}: ${hostStats.cpu_max_freq}`;
|
||||
cpu_data.setAttribute('data-original-title', cpuDataTitle);
|
||||
cpu_usage.textContent = hostStats.cpu_usage;
|
||||
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
|
||||
mem_percent.textContent = hostStats.mem_percent + '%';
|
||||
});
|
||||
});
|
||||
}
|
||||
if (webSocket) {
|
||||
cpu_data = document.getElementById('cpu_data');
|
||||
cpu_usage = document.getElementById('cpu_usage');
|
||||
mem_usage = document.getElementById('mem_usage');
|
||||
mem_percent = document.getElementById('mem_percent');
|
||||
|
||||
|
||||
webSocket.on('update_host_stats', function (hostStats) {
|
||||
var cpuDataTitle = `{% raw translate('dashboard', 'cpuCores', data['lang']) %}: ${hostStats.cpu_cores} <br /> {% raw translate("dashboard", "cpuCurFreq", data['lang']) %}: ${hostStats.cpu_cur_freq} <br /> {% raw translate("dashboard", "cpuMaxFreq", data['lang']) %}: ${hostStats.cpu_max_freq}`;
|
||||
cpu_data.setAttribute('data-original-title', cpuDataTitle);
|
||||
cpu_usage.textContent = hostStats.cpu_usage;
|
||||
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
|
||||
mem_percent.textContent = hostStats.mem_percent + '%';
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_reload', function (start_error) {
|
||||
location.reload()
|
||||
});
|
||||
}
|
||||
webSocket.on('send_start_reload', function () {
|
||||
location.reload()
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
var id = 'controls';
|
||||
var dataId = updateButton.server_id;
|
||||
var string = updateButton.string
|
||||
var id = id.concat(updateButton.server_id);
|
||||
if (updateButton.isUpdating){
|
||||
console.log(updateButton.isUpdating)
|
||||
document.getElementById(id).innerHTML = string;
|
||||
}
|
||||
else{
|
||||
window.location.reload()
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
let serverId = updateButton.server_id;
|
||||
let message = updateButton.string;
|
||||
let updating = updateButton.isUpdating;
|
||||
let id = 'controls' + serverId;
|
||||
if (updating) {
|
||||
console.log(updating)
|
||||
document.getElementById(id).innerHTML = message;
|
||||
}
|
||||
else {
|
||||
window.location.reload()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('update_server_status', update_servers_status);
|
||||
}
|
||||
|
||||
$(".clone_button").click(function () {
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'clone_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>'
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
var row;
|
||||
|
||||
function start(){
|
||||
row = event.target;
|
||||
}
|
||||
function dragover(){
|
||||
var e = event;
|
||||
e.preventDefault();
|
||||
|
||||
let children= Array.from(e.target.parentNode.parentNode.children);
|
||||
|
||||
if(children.indexOf(e.target.parentNode)>children.indexOf(row))
|
||||
e.target.parentNode.after(row);
|
||||
else
|
||||
e.target.parentNode.before(row);
|
||||
}
|
||||
|
||||
function dragend(){
|
||||
var id_string = '';
|
||||
const table = document.querySelector("table");
|
||||
for (const row of table.rows) {
|
||||
if (row.getAttribute('id') != null){
|
||||
if (id_string != ''){
|
||||
id_string += ',' + String(row.getAttribute('id'));
|
||||
}else{
|
||||
id_string +=String(row.getAttribute('id'));
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(id_string)
|
||||
sendOrder(id_string)
|
||||
}
|
||||
function sendOrder(id_string) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/send_order?order='+id_string,
|
||||
data: {
|
||||
order: id_string,
|
||||
},
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$( ".clone_button" ).click(function() {
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'clone_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientClone", data['lang']) %} </div>'
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
@ -17,7 +17,8 @@
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
<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">
|
||||
<div class="container-scroller">
|
||||
@ -28,7 +29,7 @@
|
||||
|
||||
<div class="auto-form-wrapper">
|
||||
<div class="text-center">
|
||||
<img src="/static/assets/images/logo_long.jpg"><br /><br />
|
||||
<img src="/static/assets/images/logo_long.svg"><br /><br />
|
||||
<div class="col-sm-12 grid-margin stretch-card">
|
||||
<div class="card card-statistics social-card google-card card-colored">
|
||||
<div class="card-body">
|
||||
|
@ -1,10 +1,9 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Panel Config{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -14,7 +13,8 @@
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">Panel Config</h4>
|
||||
<!-- TODO: Translate the following -->
|
||||
<h4 class="page-title">{{ translate('panelConfig', 'pageTitle', data['lang']) }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -30,22 +30,23 @@
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<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-users"></i> Users</h4>
|
||||
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang']) }}</h4>
|
||||
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
|
||||
|
||||
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> Add New User</a></div>
|
||||
<!-- TODO: Translate the following -->
|
||||
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> {{ translate('panelConfig', 'newUser', data['lang']) }}</a></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<!-- TODO: Translate the following -->
|
||||
<tr class="rounded">
|
||||
<th>User</th>
|
||||
<th>Enabled</th>
|
||||
<th>API Token</th>
|
||||
<th>Allowed Servers</th>
|
||||
<th>Assigned Roles</th>
|
||||
<th>Edit</th>
|
||||
<th>{{ translate('panelConfig', 'user', data['lang']) }}</th>
|
||||
<th>{{ translate('panelConfig', 'enabled', data['lang']) }}</th>
|
||||
<th>{{ translate('panelConfig', 'allowedServers', data['lang']) }}</th>
|
||||
<th>{{ translate('panelConfig', 'assignedRoles', data['lang']) }}</th>
|
||||
<th>{{ translate('panelConfig', 'edit', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -64,9 +65,6 @@
|
||||
|
||||
{% end %}
|
||||
</td>
|
||||
<td>
|
||||
<button data-toggle="tooltip" title="Show API Key" data-id="{{ user.api_token }}" type="button" class="btn btn-info show_button">Show</button>
|
||||
</td>
|
||||
<td id="server_list_{{user.user_id}}">
|
||||
<ul id="{{user.user_id}}">
|
||||
{% for item in data['auth-servers'][user.user_id] %}
|
||||
@ -95,19 +93,20 @@
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<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-user-tag"></i> Roles</h4>
|
||||
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles', data['lang']) }}</h4>
|
||||
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
|
||||
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> Add New Role</a></div>
|
||||
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> {{ translate('panelConfig', 'newRole', data['lang']) }}</a></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<!-- TODO: Translate the following -->
|
||||
<tr class="rounded">
|
||||
<th>Role</th>
|
||||
<th>Allowed Servers</th>
|
||||
<th>Role Users</th>
|
||||
<th>Edit</th>
|
||||
<th>{{ translate('panelConfig', 'role', data['lang']) }}</th>
|
||||
<th>{{ translate('panelConfig', 'allowedServers', data['lang']) }}</th>
|
||||
<th>{{ translate('panelConfig', 'roleUsers', data['lang']) }}</th>
|
||||
<th>{{ translate('panelConfig', 'edit', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -142,6 +141,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if data['superuser'] %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<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-user-tag"></i> {{ translate('panelConfig', 'adminControls', data['lang']) }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<button type="button" class="btn btn-outline-danger clear-comm">{{ translate('panelConfig', 'clearComms', data['lang']) }}</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -207,6 +221,17 @@ $( document ).ready(function() {
|
||||
message: api_key,
|
||||
});
|
||||
});
|
||||
|
||||
$('.clear-comm').click(function () {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/clear_comm',
|
||||
success: function (data) {
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -1,10 +1,9 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Edit Role{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('rolesConfig', 'pageTitle', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -16,13 +15,13 @@
|
||||
<div class="page-header">
|
||||
{% if data['new_role'] %}
|
||||
<h4 class="page-title">
|
||||
New Role
|
||||
{{ translate('rolesConfig', 'pageTitleNew', data['lang']) }}
|
||||
<br />
|
||||
<small>RID: N/A</small>
|
||||
</h4>
|
||||
{% else %}
|
||||
<h4 class="page-title">
|
||||
Edit Role - {{ data['role']['role_name'] }}
|
||||
{{ translate('rolesConfig', 'pageTitle', data['lang']) }} - {{ data['role']['role_name'] }}
|
||||
<br />
|
||||
<small>RID: {{ data['role']['role_id'] }}</small>
|
||||
</h4>
|
||||
@ -41,7 +40,7 @@
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<i class="fas fa-cogs"></i>Config</a>
|
||||
<i class="fas fa-cogs"></i>{{ translate('rolesConfig', 'config', data['lang']) }}</a>
|
||||
</li>
|
||||
<!-- <li class="nav-item">
|
||||
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
@ -58,22 +57,22 @@
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['role']['role_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
|
||||
<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-user-tag"></i> Role Settings</h4>
|
||||
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('rolesConfig', 'roleTitle', data['lang']) }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="role_name">Role Name <small class="text-muted ml-1"> - What you wish to call this role</small> </label>
|
||||
<label for="role_name">{{ translate('rolesConfig', 'roleName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('rolesConfig', 'roleDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="role_name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<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-server"></i> Allowed Servers <small class="text-muted ml-1"> - servers this role is allowed to access </small> </h4>
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('rolesConfig', 'roleServers', data['lang']) }} <small class="text-muted ml-1"> {{ translate('rolesConfig', 'serversDesc', data['lang']) }}</small> </h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
@ -81,8 +80,8 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Server Name</th>
|
||||
<th>Access?</th>
|
||||
<th>{{ translate('rolesConfig', 'serverName', data['lang']) }}</th>
|
||||
<th>{{ translate('rolesConfig', 'serverAccess', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -105,19 +104,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<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-user-lock"></i> Roles Permissions <small class="text-muted ml-1"> - permissions this role has on this/these servers </small></h4>
|
||||
<h4 class="card-title"><i class="fas fa-user-lock"></i> {{ translate('rolesConfig', 'rolePerms', data['lang']) }}<small class="text-muted ml-1"> - {{ translate('rolesConfig', 'permsServer', data['lang']) }} </small></h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Permission Name</th>
|
||||
<th>Authorized ?</th>
|
||||
<th>{{ translate('rolesConfig', 'permName', data['lang']) }}</th>
|
||||
<th>{{ translate('rolesConfig', 'permAccess', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -149,14 +148,14 @@
|
||||
<div class="col-md-3 col-sm-12">
|
||||
<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-users"></i> Users Assigned to Role:</h4>
|
||||
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('rolesConfig', 'roleUsers', data['lang']) }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>User Name</th>
|
||||
<th>{{ translate('rolesConfig', 'roleUserName', data['lang']) }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -183,22 +182,22 @@
|
||||
<div class="col-md-3 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Role Config Area</h4>
|
||||
<p class="card-description"> Here is where you can change the configuration of your role</p>
|
||||
<h4 class="card-title">{{ translate('rolesConfig', 'roleConfigArea', data['lang']) }}</h4>
|
||||
<p class="card-description"> {{ translate('rolesConfig', 'configDesc', data['lang']) }}</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
Created: {{ str(data['role']['created']) }}
|
||||
{{ translate('rolesConfig', 'created', data['lang']) }} {{ str(data['role']['created']) }}
|
||||
<br />
|
||||
Last updated: {{ str(data['role']['last_update']) }}
|
||||
{{ translate('rolesConfig', 'configUpdate', data['lang']) }} {{ str(data['role']['last_update']) }}
|
||||
<br />
|
||||
</p>
|
||||
</blockquote>
|
||||
<div class="text-center">
|
||||
{% if data['new_role'] %}
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete Role</a><br />
|
||||
<small>You cannot delete something that does not yet exist</small>
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a><br />
|
||||
<small>{{ translate('rolesConfig', 'doesNotExist', data['lang']) }}</small>
|
||||
{% else %}
|
||||
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i> Delete Role</a>
|
||||
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
@ -229,7 +228,6 @@
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Edit User{% end %}
|
||||
@ -16,13 +15,13 @@
|
||||
<div class="page-header">
|
||||
{% if data['new_user'] %}
|
||||
<h4 class="page-title">
|
||||
New User
|
||||
{{ translate('userConfig', 'pageTitleNew', data['lang']) }}
|
||||
<br />
|
||||
<small>UID: N/A</small>
|
||||
</h4>
|
||||
{% else %}
|
||||
<h4 class="page-title">
|
||||
Edit User - {{ data['user']['user_id'] }}
|
||||
{{ translate('userConfig', 'pageTitle', data['lang']) }} - {{ data['user']['user_id'] }}
|
||||
<br />
|
||||
<small>UID: {{ data['user']['user_id'] }}</small>
|
||||
</h4>
|
||||
@ -41,12 +40,12 @@
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/panel/{{ 'add_user' if data['new_user'] else 'edit_user' }}?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<i class="fas fa-cogs"></i>Config</a>
|
||||
<i class="fas fa-cogs"></i> {{ translate('userConfig', 'config', data['lang']) }} - {{ data['user']['user_id'] }}</a>
|
||||
</li>
|
||||
{% if not data['new_user'] %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/add_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<i class="fas fa-folder-tree"></i>Other</a>
|
||||
<a class="nav-link" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}" role="tab" aria-selected="false">
|
||||
<i class="fas fa-key"></i>{{ translate('userConfig', 'apiKey', data['lang']) }} - {{ data['user']['user_id'] }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
</ul>
|
||||
@ -65,26 +64,34 @@
|
||||
|
||||
<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-user"></i> User Settings</h4>
|
||||
<h4 class="card-title"><i class="fas fa-user"></i> {{ translate('userConfig', 'userSettings', data['lang']) }} - {{ data['user']['user_id'] }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="username">User Name <small class="text-muted ml-1"> - What you wish to call this user</small> </label>
|
||||
<label class="form-label" for="username">{{ translate('userConfig', 'userName', data['lang']) }} - {{ data['user']['user_id'] }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'userNameDesc', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
|
||||
<input type="text" class="form-control" name="username" id="username" value="{{ data['user']['username'] }}" placeholder="User Name" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="password0">Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
|
||||
<label class="form-label" for="password0">{{ translate('userConfig', 'password', data['lang']) }} - {{ data['user']['user_id'] }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
|
||||
<input type="password" class="form-control" name="password0" id="password0" value="" placeholder="Password" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="password1">Repeat Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
|
||||
<label class="form-label" for="password1">{{ translate('userConfig', 'repeat', data['lang']) }} - {{ data['user']['user_id'] }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
|
||||
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="language">User Language:</label>
|
||||
<select class="form-select" id="language" name="language" form="user_form">
|
||||
<label class="form-label" for="email">{{ translate('userConfig', 'gravEmail', data['lang']) }} - {{ data['user']['user_id'] }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'gravDesc', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
|
||||
<input type="email" class="form-control" name="email" id="email" value="{{ data['user']['email'] }}" placeholder="Gravatar Email" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="language">{{ translate('userConfig', 'userLang', data['lang']) }}</label>
|
||||
<select class="form-select form-control form-control-lg select-css" id="language" name="language" form="user_form">
|
||||
{% for lang in data['languages'] %}
|
||||
{% if not 'incomplete' in lang %}
|
||||
<option value="{{lang}}">{{lang}}</option>
|
||||
{% else %}
|
||||
<option value="{{lang}}" disabled>{{lang}}</option>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</select>
|
||||
</div>
|
||||
@ -93,7 +100,7 @@
|
||||
|
||||
<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-user-tag"></i> Roles <small class="text-muted ml-1"> - the roles this user is a member of</small></h4>
|
||||
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('userConfig', 'userRoles', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'userRolesDesc', data['lang']) }}</small></h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
@ -101,8 +108,8 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Role Name</th>
|
||||
<th>Member?</th>
|
||||
<th>{{ translate('userConfig', 'roleName', data['lang']) }}</th>
|
||||
<th>{{ translate('userConfig', 'member', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -130,7 +137,7 @@
|
||||
|
||||
<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-user-lock"></i> Crafty Permissions <small class="text-muted ml-1"> - permissions this user has on Crafty Controller </small></h4>
|
||||
<h4 class="card-title"><i class="fas fa-user-lock"></i> {{ translate('userConfig', 'craftyPerms', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'craftyPermDesc', data['lang']) }}</small></h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
@ -138,9 +145,9 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Permission Name</th>
|
||||
<th>Authorized ?</th>
|
||||
<th>Quantity</th>
|
||||
<th>{{ translate('userConfig', 'permName', data['lang']) }}</th>
|
||||
<th>{{ translate('userConfig', 'auth', data['lang']) }}</th>
|
||||
<th>{{ translate('userConfig', 'uses', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -167,25 +174,17 @@
|
||||
<div class="form-check-flat">
|
||||
<label for="enabled" class="form-check-label ml-4 mb-4">
|
||||
{% if data['user']['enabled'] %}
|
||||
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">Enabled
|
||||
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">{{ translate('userConfig', 'enabled', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" value="1">Enabled
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
<label for="regen_api" class="form-check-label ml-4 mb-4">
|
||||
{% if data['new_user'] %}
|
||||
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" checked="" value="1" disabled >Regenerate API Key
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" value="1">Regenerate API Key
|
||||
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" value="1">{{ translate('userConfig', 'enabled', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
<label for="superuser" class="form-check-label ml-4 mb-4">
|
||||
{% if data['user']['superuser'] %}
|
||||
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" checked="" value="1" disabled >Super User
|
||||
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" checked="" value="1" {{ data['super-disabled'] }} >{{ translate('userConfig', 'super', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" value="1" disabled >Super User
|
||||
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" {{ data['super-disabled'] }} value="1" >{{ translate('userConfig', 'super', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
@ -199,19 +198,17 @@
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><i class="fas fa-user-cog"></i> User Config Area</h4>
|
||||
<p class="card-description"> Here is where you can change the configuration of your user</p>
|
||||
<h4 class="card-title"><i class="fas fa-user-cog"></i> {{ translate('userConfig', 'configArea', data['lang']) }}</h4>
|
||||
<p class="card-description"> {{ translate('userConfig', 'configAreaDesc', data['lang']) }}</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
Created: {{ str(data['user']['created']) }}
|
||||
{{ translate('userConfig', 'created', data['lang']) }} {{ str(data['user']['created']) }}
|
||||
<br />
|
||||
Last login: {{ str(data['user']['last_login']) }}
|
||||
{{ translate('userConfig', 'lastLogin', data['lang']) }} {{ str(data['user']['last_login']) }}
|
||||
<br />
|
||||
Last update: {{ str(data['user']['last_update']) }}
|
||||
{{ translate('userConfig', 'lastUpdate', data['lang']) }} {{ str(data['user']['last_update']) }}
|
||||
<br />
|
||||
Last IP: {{ data['user']['last_ip'] }}
|
||||
<br />
|
||||
API Key: {{ data['user']['api_token'] }}
|
||||
{{ translate('userConfig', 'lastIP', data['lang']) }} {{ data['user']['last_ip'] }}
|
||||
<br />
|
||||
</p>
|
||||
</blockquote>
|
||||
@ -219,13 +216,13 @@
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% if data['new_user'] %}
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete User</a><br />
|
||||
<small>You cannot delete something that does not yet exist</small>
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i>{{ translate('userConfig', 'deleteUserB', data['lang']) }}</a><br />
|
||||
<small>{{ translate('userConfig', 'notExist', data['lang']) }}</small>
|
||||
{% elif data['user']['superuser'] %}
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete User</a><br />
|
||||
<small>You cannot delete a superuser</small>
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> {{ translate('userConfig', 'deleteUserB', data['lang']) }}</a><br />
|
||||
<small>{{ translate('userConfig', 'delSuper', data['lang']) }}</small>
|
||||
{% else %}
|
||||
<a href="/panel/remove_user?id={{ data['user']['user_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i> Delete User</a>
|
||||
<button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> {{ translate('userConfig', 'deleteUserB', data['lang']) }}</a>
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
@ -246,6 +243,60 @@
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
const userId = new URLSearchParams(document.location.search).get('id')
|
||||
|
||||
$( ".delete-user" ).click(function() {
|
||||
var file_to_del = $(this).data("file");
|
||||
|
||||
console.log("User to delete is "+userId);
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{% raw translate('userConfig', 'deleteUser', data['lang']) %} "+userId,
|
||||
message: "{{ translate('userConfig', 'confirmDelete', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
className: 'btn-outline-danger',
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
location.href="/panel/remove_user?id="+userId;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function superConfirm() {
|
||||
if (document.getElementById('superuser').checked){
|
||||
bootbox.confirm({
|
||||
title: "{{ translate('panelConfig', 'superConfirmTitle', data['lang']) }}",
|
||||
message: "{{ translate('panelConfig', 'superConfirm', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fa fa-times"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
className: 'btn-outline-warning',
|
||||
label: '<i class="fa fa-check"></i> {{ translate('serverBackups', 'confirm', data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result == true){
|
||||
return;
|
||||
}else{
|
||||
document.getElementById('superuser').checked = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}else{
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
|
252
app/frontend/templates/panel/panel_edit_user_apikeys.html
Normal file
252
app/frontend/templates/panel/panel_edit_user_apikeys.html
Normal file
@ -0,0 +1,252 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - Edit User API Keys{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('apiKeys', 'pageTitle', data['lang']) }} - {{ data['user']['user_id'] }}
|
||||
<br/>
|
||||
<small>UID: {{ data['user']['user_id'] }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<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">
|
||||
<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">
|
||||
<i class="fas fa-key"></i>{{ translate('apiKeys', 'apiKeys', data['lang']) }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7 col-sm-12">
|
||||
<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-key"></i>{{ translate('apiKeys', 'apiKeys', data['lang']) }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<!--<th>ID</th>-->
|
||||
<th>{{ translate('apiKeys', 'name', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'created', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'superUser', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'perms', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'buttons', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for apikey in data['api_keys'] %}
|
||||
<tr>
|
||||
<!--<td>{-{ apikey.token_id }-}</td>-->
|
||||
<td>{{ apikey.name }}</td>
|
||||
<td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td>
|
||||
<td>
|
||||
{% if apikey.superuser %}
|
||||
<span class="text-success">
|
||||
<i class="fas fa-check-square"></i> {{ translate('apiKeys', 'yes', data['lang']) }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-danger">
|
||||
<i class="far fa-times-square"></i> {{ translate('apiKeys', 'no', data['lang']) }}
|
||||
</span>
|
||||
{% end %}
|
||||
</td>
|
||||
<td>{{ translate('apiKeys', 'server', data['lang']) }} {{ 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', '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', 'getToken', data['lang']) }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5 col-sm-12">
|
||||
<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-plus"></i> {{ translate('apiKeys', 'createNew', data['lang']) }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="user_form" class="forms-sample" method="post"
|
||||
action="/panel/edit_user_apikeys">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
|
||||
|
||||
<div class="form-group">
|
||||
<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">
|
||||
</div>
|
||||
|
||||
<table class="table table-hover mb-3">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>{{ translate('apiKeys', 'permName', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'auth', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for permission in data['server_permissions_all'] %}
|
||||
<tr>
|
||||
<td><label
|
||||
for="permission_{{ permission.name }}">{{ permission.name }}</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" class=""
|
||||
id="permission_{{ permission.name }}"
|
||||
name="permission_{{ permission.name }}" value="1">
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
{% for permission in data['crafty_permissions_all'] %}
|
||||
<tr>
|
||||
<td><label
|
||||
for="permission_{{ permission.name }}">{{ permission.name }}</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" class=""
|
||||
id="permission_{{ permission.name }}"
|
||||
name="permission_{{ permission.name }}" value="1">
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<label for="superuser">Superuser</label>
|
||||
<input type="checkbox" class="" id="superuser"
|
||||
name="superuser" value="1">
|
||||
|
||||
<br/>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-plus"></i>
|
||||
Create
|
||||
</button>
|
||||
<button type="reset" class="btn btn-light"><i
|
||||
class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("ready!");
|
||||
$('.delete-api-key').click(function () {
|
||||
var keyId = $(this).data("key-id");
|
||||
var keyName = $(this).data("key-name");
|
||||
bootbox.confirm({
|
||||
title: `Remove API key ${keyName}?`,
|
||||
message: "Do you want to delete this API key? This cannot be undone.",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("panelConfig", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/panel/remove_apikey?id=' + keyId,
|
||||
success: function (data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
$('.get-a-token').click(function () {
|
||||
var keyId = $(this).data("key-id");
|
||||
var keyName = $(this).data("key-name");
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/panel/get_token?id=' + keyId,
|
||||
success: function (data) {
|
||||
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;">${data}</pre>`
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -1,54 +1,58 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-3 pb-3">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 mr-2">
|
||||
{% if data['server_stats']['running'] %}
|
||||
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }} ({{ translate('serverStats', 'serverTime', data['lang']) }})</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }}</span>
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 mr-2">
|
||||
<b>{{ translate('serverStats', 'cpuUsage', data['lang']) }}:</b> {{ data['server_stats']['cpu'] }}% <br />
|
||||
<b>{{ translate('serverStats', 'memUsage', data['lang']) }}:</b> {{ data['server_stats']['mem'] }} <br />
|
||||
{% if data['server_stats']['int_ping_results'] %}
|
||||
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> {{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}<br />
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> 0/0<br />
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 mr-2">
|
||||
{% if data['server_stats']['version'] != 'False' %}
|
||||
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ data['server_stats']['version'] }} <br />
|
||||
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ data['server_stats']['desc'] }}</span> <br />
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
|
||||
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-3 pb-3">
|
||||
<div class="row">
|
||||
<div class="col-sm-4 mr-2">
|
||||
{% 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 />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }}</span>
|
||||
{% elif data['server_stats']['crashed'] %}
|
||||
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-danger"> <i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started" class="text-danger"> <i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime" class="text-danger"> <i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}</span>
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span>
|
||||
{% end %}
|
||||
<br>
|
||||
<b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{ data['serverTZ'] }}</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 mr-2">
|
||||
<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'] %}
|
||||
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> <span id="players" >{{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}</span><br />
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> <span id="players" >0/0</span><br />
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 mr-2">
|
||||
{% 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" class="input_motd">{{ data['server_stats']['desc'] }}</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 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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/static/assets/vendors/moment/moment.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
|
||||
function durationToHumanizedString (duration) {
|
||||
function durationToHumanizedString(duration) {
|
||||
duration._data.months += duration._data.years * 12;
|
||||
// 30.45833333333 = average month length, calculate with (31+28.5+31+30+31+30+31+31+30+31+30+31) / 12
|
||||
duration._data.days += duration._data.months * 30.45833333333;
|
||||
@ -62,7 +66,7 @@
|
||||
|
||||
output = Object.entries(obj)
|
||||
.map(([type, num]) => {
|
||||
// make them strings
|
||||
// make them strings
|
||||
returnData = num + ' ' + type;
|
||||
// remove the s in the end if the data is -1 or 1
|
||||
if (num == -1 || num == 1)
|
||||
@ -71,27 +75,28 @@
|
||||
})
|
||||
.map((v, i, a) => // example input: [1,2,3], output: "1, 2 and 3"
|
||||
v + (i !== a.length - 1
|
||||
? i !== a.length - 2
|
||||
? i !== a.length - 2
|
||||
? ', '
|
||||
: ' and '
|
||||
: '')).join('');
|
||||
return output;
|
||||
}
|
||||
|
||||
let uptime = document.querySelector('#uptime');
|
||||
let started = document.querySelector('#started');
|
||||
let startedUTC;
|
||||
let startedLocal;
|
||||
let uptimeLoop;
|
||||
|
||||
document.body.onload = (() => {
|
||||
|
||||
console.log('calculateTime');
|
||||
let uptime = document.querySelector('#uptime');
|
||||
let started = document.querySelector('#started');
|
||||
let startedUTC;
|
||||
let startedLocal;
|
||||
|
||||
if (started != null) {
|
||||
startedUTC = '{{ data['server_stats']['started'] }}';
|
||||
startedUTC = "{{ data['server_stats']['started'] }}";
|
||||
if (startedUTC != 'False') {
|
||||
console.log('started utc:', startedUTC);
|
||||
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
let browserUTCOffset = moment().utcOffset(); // This is in minutes
|
||||
var browserUTCOffset = moment().utcOffset(); // This is in minutes
|
||||
|
||||
startedLocal = startedUTC.utcOffset(browserUTCOffset);
|
||||
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
|
||||
@ -102,23 +107,110 @@
|
||||
}
|
||||
|
||||
var calculateUptime = () => {
|
||||
var msdiff = moment()
|
||||
.diff(startedLocal);
|
||||
var msdiff = moment().diff(startedLocal);
|
||||
var diff = moment.duration(msdiff);
|
||||
|
||||
uptime.textContent = durationToHumanizedString(diff);
|
||||
}
|
||||
|
||||
if (uptime != null && started != null) {
|
||||
|
||||
console.log('startedLocal', startedLocal)
|
||||
if (startedLocal) {
|
||||
calculateUptime()
|
||||
var uptimeLoop = setInterval(calculateUptime, 1000)
|
||||
calculateUptime();
|
||||
uptimeLoop = setInterval(calculateUptime, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
initParser('input_motd', 'input_motd');
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
function update_server_details(server) {
|
||||
server_status = document.getElementById('status');
|
||||
server_started = document.getElementById('started');
|
||||
server_uptime = document.getElementById('uptime');
|
||||
server_cpu = document.getElementById('cpu');
|
||||
server_mem = document.getElementById('mem');
|
||||
server_players = document.getElementById('players');
|
||||
server_version = document.getElementById('version');
|
||||
server_input_motd = document.getElementById('input_motd');
|
||||
|
||||
/* TODO Update each element */
|
||||
if (server.running){
|
||||
server_status.setAttribute("class", "text-success");
|
||||
server_status.innerHTML = `{{ translate('serverStats', 'online', data['lang']) }}`;
|
||||
|
||||
startedUTC = server.started;
|
||||
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
|
||||
var browserUTCOffset = moment().utcOffset(); // This is in minutes
|
||||
startedLocal = startedUTC.utcOffset(browserUTCOffset);
|
||||
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
|
||||
server_started.setAttribute("class", "");
|
||||
server_started.innerHTML = startedLocalFormatted;
|
||||
server_uptime.setAttribute("class", "");
|
||||
if (!uptimeLoop) {
|
||||
var calculateUptime = () => {
|
||||
var msdiff = moment().diff(startedLocal);
|
||||
var diff = moment.duration(msdiff);
|
||||
uptime.textContent = durationToHumanizedString(diff);
|
||||
}
|
||||
uptimeLoop = setInterval(calculateUptime, 1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (server.crashed){
|
||||
server_status.setAttribute("class", "text-danger");
|
||||
server_status.innerHTML = `<i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}`;
|
||||
server_started.setAttribute("class", "text-danger");
|
||||
server_started.innerHTML = `<i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}`;
|
||||
clearInterval(uptimeLoop);
|
||||
uptimeLoop = null;
|
||||
server_uptime.setAttribute("class", "text-danger");
|
||||
server_uptime.innerHTML = `<i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}`;
|
||||
}else{
|
||||
server_status.setAttribute("class", "text-warning");
|
||||
server_status.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
|
||||
server_started.setAttribute("class", "text-warning");
|
||||
server_started.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
|
||||
clearInterval(uptimeLoop);
|
||||
uptimeLoop = null;
|
||||
server_uptime.setAttribute("class", "text-warning");
|
||||
server_uptime.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
|
||||
}
|
||||
}
|
||||
|
||||
server_cpu.innerHTML = server.cpu + ` %`;
|
||||
server_mem.innerHTML = server.mem;
|
||||
|
||||
if (server.int_ping_results)
|
||||
{
|
||||
server_players.innerHTML = server.online + `/` + server.max;
|
||||
}
|
||||
else
|
||||
{
|
||||
server_players.innerHTML = `0/0`;
|
||||
}
|
||||
|
||||
if (server.version)
|
||||
{
|
||||
server_version.innerHTML = server.version;
|
||||
server_input_motd.innerHTML = server.desc;
|
||||
}
|
||||
else
|
||||
{
|
||||
server_version.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`;
|
||||
server_input_motd.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`;
|
||||
}
|
||||
|
||||
initParser('input_motd', 'input_motd');
|
||||
|
||||
}
|
||||
|
||||
$(window).ready(function () {
|
||||
console.log("ready!");
|
||||
|
||||
//if (webSocket) {
|
||||
webSocket.on('update_server_details', update_server_details);
|
||||
//}
|
||||
});
|
||||
</script>
|
||||
|
@ -5,7 +5,8 @@
|
||||
<i class="fas fa-file-signature"></i>{{ translate('serverDetails', 'terminal', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Logs'] in data['user_permissions'] %}
|
||||
<!--Bedrock servers don't have logs so we'll only show it if we know it's not a bedrock server.-->
|
||||
{% if data['permissions']['Logs'] in data['user_permissions'] and data['server_data']['type'] != 'minecraft-bedrock'%}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'logs' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="false">
|
||||
<i class="fas fa-file-signature"></i>{{ translate('serverDetails', 'logs', data['lang']) }}</a>
|
||||
@ -13,7 +14,7 @@
|
||||
{% end %}
|
||||
{% if data['permissions']['Schedule'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'schedule' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<a class="nav-link {% if data['active_link'] == 'schedules' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
@ -35,7 +36,7 @@
|
||||
<i class="fas fa-cogs"></i>{{ translate('serverDetails', 'config', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Players'] in data['user_permissions'] %}
|
||||
{% if data['permissions']['Players'] in data['user_permissions'] and data['server_data']['type'] != 'minecraft-bedrock' %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'admin_controls' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
|
||||
<i class="fas fa-users"></i>{{ translate('serverDetails', 'playerControls', data['lang']) }}</a>
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
@ -33,7 +32,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<style>
|
||||
@ -86,7 +85,7 @@
|
||||
<li class="playerItem banned">
|
||||
<h3>{{ translate('serverPlayerManagement', 'loadingBannedPlayers', data['lang']) }}</h3>
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
@ -36,6 +35,8 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<br>
|
||||
<br>
|
||||
<form class="forms-sample" method="post" action="/panel/server_backup">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
@ -45,23 +46,58 @@
|
||||
<a href="/panel/backup_now?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary" onclick="backup_started()">{{ translate('serverBackups', 'backupNow', data['lang']) }}</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{% if data['super_user'] %}
|
||||
<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_path" id="backup_path" value="{{ data['server_stats']['server_id']['backup_path'] }}" 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']) }}</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']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="superuser" class="form-check-label ml-4 mb-4">
|
||||
{% if data['backup_config']['auto_enabled'] %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" checked="" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
|
||||
<label for="compress" class="form-check-label ml-4 mb-4"></label>
|
||||
{% if data['backup_config']['compress'] %}
|
||||
<input type="checkbox" class="form-check-input" id="compress" name="compress"
|
||||
checked="" value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
|
||||
<input type="checkbox" class="form-check-input" id="compress" name="compress"
|
||||
value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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">{{ translate('serverBackups', 'clickExclude', data['lang']) }}</button>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="changed" id="changed" value="0" style="visibility: hidden;"></input>
|
||||
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups', 'excludedChoose', data['lang']) }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
|
||||
<input type="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>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{{ translate('serverFiles', 'files', data['lang']) }}
|
||||
</span>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
|
||||
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang']) }}</button>
|
||||
@ -70,15 +106,10 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{ translate('serverBackups', 'currentBackups', data['lang']) }}</h4>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
|
||||
<table class="table table-responsive dataTable" id="backup_table">
|
||||
<h4 class="card-title">{{ translate('serverBackups', 'currentBackups', data['lang']) }}</h4>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">{{ translate('serverBackups', 'options', data['lang']) }}</th>
|
||||
@ -96,10 +127,14 @@
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
<button data-file="{{ backup['path'] }}" class="btn btn-danger del_button">
|
||||
<button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}" class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'delete', data['lang']) }}
|
||||
</button>
|
||||
<button data-file="{{ backup['path'] }}" class="btn btn-warning restore_button">
|
||||
<i class="fas fa-undo-alt" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'restore', data['lang']) }}
|
||||
</button>
|
||||
</td>
|
||||
<td>{{ backup['path'] }}</td>
|
||||
<td>{{ backup['size'] }}</td>
|
||||
@ -112,6 +147,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 col-sm-12">
|
||||
<br>
|
||||
<br>
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups', data['lang']) }} <small class="text-muted ml-1"></small> </h4>
|
||||
</div>
|
||||
<br>
|
||||
<ul>
|
||||
{% for item in data['exclusions'] %}
|
||||
<li>{{item}}</li>
|
||||
<br>
|
||||
{% end %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -121,6 +170,44 @@
|
||||
|
||||
|
||||
</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>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
@ -128,91 +215,278 @@
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
const server_id = new URLSearchParams(document.location.search).get('id')
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
function backup_started(time='5-10') {
|
||||
bootbox.alert({
|
||||
message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}",
|
||||
backdrop: true
|
||||
});
|
||||
}
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
function del_backup(filename, id){
|
||||
var token = getCookie("_xsrf")
|
||||
function backup_started(time='5-10') {
|
||||
bootbox.alert({
|
||||
message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}",
|
||||
backdrop: true
|
||||
});
|
||||
}
|
||||
|
||||
data_to_send = { file_name :filename}
|
||||
function del_backup(filename, id){
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
console.log('Sending Command to delete backup: ' + filename)
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_file?server_id='+id,
|
||||
data: {
|
||||
file_path: filename,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
data_to_send = { file_name :filename}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
$("#backup_config_box").hide();
|
||||
$("#backup_save_note").hide();
|
||||
console.log('Sending Command to delete backup: ' + filename)
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_backup?server_id='+id,
|
||||
data: {
|
||||
file_path: filename,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$("#show_config").click(function () {
|
||||
$("#backup_config_box").toggle();
|
||||
$('#backup_button').hide();
|
||||
$('#backup_save_note').show();
|
||||
$('#backup_data').hide();
|
||||
});
|
||||
function restore_backup(filename, id){
|
||||
var token = getCookie("_xsrf")
|
||||
var dialog = bootbox.dialog({
|
||||
message: '<i class="fa fa-spin fa-spinner"></i> {{ translate('serverBackups', 'restoring', data['lang']) }}',
|
||||
closeButton: false
|
||||
});
|
||||
|
||||
$('#backup_table').DataTable({
|
||||
"order": [[ 1, "desc" ]],
|
||||
"paging":true,
|
||||
"lengthChange": false,
|
||||
"searching": true,
|
||||
"ordering": true,
|
||||
"info": true,
|
||||
"autoWidth": false,
|
||||
"responsive": true,
|
||||
});
|
||||
console.log('Sending Command to restore backup: ' + filename)
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/restore_backup?server_id='+id,
|
||||
data: {
|
||||
zip_file: filename,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
setTimeout(function(){
|
||||
location.href=('/panel/dashboard');
|
||||
}, 15000);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$( ".del_button" ).click(function() {
|
||||
var file_to_del = $(this).data("file");
|
||||
|
||||
console.log("file to delete is" + file_to_del);
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{% raw translate('serverBackups', 'destroyBackup', data['lang']) %}",
|
||||
message: "{{ translate('serverBackups', 'confirmDelete', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
var full_path = '{{ data['backup_path'] }}' + '/' + file_to_del;
|
||||
del_backup(full_path, {{ data['server_stats']['server_id']['server_id'] }} );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
$("#backup_config_box").hide();
|
||||
$("#backup_save_note").hide();
|
||||
|
||||
$("#show_config").click(function () {
|
||||
$("#backup_config_box").toggle();
|
||||
$('#backup_button').hide();
|
||||
$('#backup_save_note').show();
|
||||
$('#backup_data').hide();
|
||||
});
|
||||
|
||||
$('#backup_table').DataTable({
|
||||
"order": [[ 1, "desc" ]],
|
||||
"paging":true,
|
||||
"lengthChange": false,
|
||||
"searching": true,
|
||||
"ordering": true,
|
||||
"info": true,
|
||||
"autoWidth": false,
|
||||
"responsive": true,
|
||||
});
|
||||
|
||||
$( ".del_button" ).click(function() {
|
||||
var file_to_del = $(this).data("file");
|
||||
var backup_path = $(this).data('backup_path');
|
||||
|
||||
console.log("file to delete is" + file_to_del);
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{% raw translate('serverBackups', 'destroyBackup', data['lang']) %}",
|
||||
message: "{{ translate('serverBackups', 'confirmDelete', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
var full_path = backup_path + '/' + file_to_del;
|
||||
del_backup(full_path, server_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( ".restore_button" ).click(function() {
|
||||
var file_to_restore = $(this).data("file");
|
||||
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{{ translate('serverBackups', 'restore', data['lang']) }} "+file_to_restore,
|
||||
message: "{{ translate('serverBackups', 'confirmRestore', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "restore", data['lang']) }}',
|
||||
className: 'btn-outline-danger'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
restore_backup(file_to_restore, server_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
document.getElementById("modal-cancel").addEventListener("click", function(){
|
||||
document.getElementById("root_files_button").classList.remove('clicked');
|
||||
document.getElementById("main-tree-div").innerHTML = '<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><i class="far fa-folder-open"></i>{{ translate("serverFiles", "files", data["lang"]) }}</span></input>'
|
||||
})
|
||||
|
||||
document.getElementById("root_files_button").addEventListener("click", function(){
|
||||
if($("#root_files_button").data('server_path') != ""){
|
||||
if(document.getElementById('root_files_button').classList.contains('clicked')){
|
||||
show_file_tree();
|
||||
return;
|
||||
}else{
|
||||
document.getElementById('root_files_button').classList.add('clicked');
|
||||
document.getElementById("changed").value = 1;
|
||||
}
|
||||
path = $("#root_files_button").data('server_path')
|
||||
console.log($("#root_files_button").data('server_path'))
|
||||
var token = getCookie("_xsrf");
|
||||
var dialog = bootbox.dialog({
|
||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||
closeButton: false
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/backup_select?id='+server_id+'&path='+path,
|
||||
});
|
||||
}else{
|
||||
bootbox.alert("You must input a path before selecting this button");
|
||||
}
|
||||
});
|
||||
if (webSocket) {
|
||||
webSocket.on('send_temp_path', function (data) {
|
||||
setTimeout(function(){
|
||||
var x = document.querySelector('.bootbox');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
||||
getTreeView(data.path);
|
||||
show_file_tree();
|
||||
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function getTreeView(path) {
|
||||
path = path
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_backup_tree?id='+server_id+'&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try{
|
||||
document.getElementById('main-tree-div').innerHTML += text;
|
||||
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||
}catch{
|
||||
document.getElementById('files-tree').innerHTML = text;
|
||||
}
|
||||
|
||||
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
function getToggleMain(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
document.getElementById("files-tree").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret");
|
||||
}
|
||||
|
||||
|
||||
function getDirView(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
|
||||
if (document.getElementById(path).classList.contains('clicked')){
|
||||
|
||||
var toggler = document.getElementById(path+"span");
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_backup_dir?id='+server_id+'&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try{
|
||||
document.getElementById(path+"span").classList.add('tree-caret-down');
|
||||
document.getElementById(path).innerHTML += text;
|
||||
document.getElementById(path).classList.add("clicked");
|
||||
}catch{
|
||||
console.log("Bad")
|
||||
}
|
||||
|
||||
var toggler = document.getElementById(path);
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"span").addEventListener("click", function caretListener() {
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
function show_file_tree(){
|
||||
$("#dir_select").modal();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
@ -10,14 +9,15 @@
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
|
||||
data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -25,127 +25,187 @@
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
{% include "parts/details_stats.html" %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
{% include "parts/server_controls_list.html" %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<form class="forms-sample" method="post" action="/panel/server_detail">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<form class="forms-sample" method="post" action="/panel/server_detail">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats']['server_id']['execution_command'] }}" placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" >
|
||||
</div>
|
||||
|
||||
<div class="form-check-flat">
|
||||
<label for="auto_start" class="form-check-label ml-4 mb-4">
|
||||
{% if data['server_stats']['server_id']['auto_start'] %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked="" value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
<label for="crash_detection" class="form-check-label ml-4 mb-4">
|
||||
{% if data['server_stats']['server_id']['crash_detection'] %}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" checked="" value="1">{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" value="1" >{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
</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" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
|
||||
</form>
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small>
|
||||
</label>
|
||||
<input type="text" class="form-control" name="server_name" id="server_name"
|
||||
value="{{ data['server_stats']['server_id']['server_name'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{ translate('serverConfigHelp', 'title', data['lang']) }}</h4>
|
||||
<p class="card-description"> {{ translate('serverConfigHelp', 'desc', data['lang']) }}</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
{% raw translate('serverConfigHelp', 'perms', data['lang']) %}
|
||||
</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% if data['server_stats']['running'] %}
|
||||
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig', 'update', data['lang']) }}</button>
|
||||
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang']) }}</a><br />
|
||||
<small>{{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }}</small>
|
||||
{% else %}
|
||||
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig', 'update', data['lang']) }}</button>
|
||||
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer', data['lang']) }}</button>
|
||||
{% end %}
|
||||
<div class="form-group">
|
||||
{% if data['super_user'] %}
|
||||
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
|
||||
</label>
|
||||
<input type="text" class="form-control" name="server_path" id="server_path"
|
||||
value="{{ data['server_stats']['server_id']['path'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" required>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="text" class="form-control" name="log_path" id="log_path"
|
||||
value="{{ data['server_stats']['server_id']['log_path'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="text" class="form-control" name="executable" id="executable"
|
||||
value="{{ data['server_stats']['server_id']['executable'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
|
||||
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc',
|
||||
data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="execution_command" id="execution_command"
|
||||
value="{{ data['server_stats']['server_id']['execution_command'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="text" class="form-control" name="stop_command" id="stop_command"
|
||||
value="{{ data['server_stats']['server_id']['stop_command'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }}
|
||||
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc',
|
||||
data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay"
|
||||
value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10"
|
||||
required>
|
||||
</div>
|
||||
|
||||
{% if data['super_user'] %}
|
||||
<div class="form-group">
|
||||
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}
|
||||
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang'])
|
||||
}}</small> </label>
|
||||
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url"
|
||||
value="{{ data['server_stats']['server_id']['executable_update_url'] }}"
|
||||
placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small
|
||||
class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small>
|
||||
</label>
|
||||
<input type="text" class="form-control" name="server_ip" id="server_ip"
|
||||
value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small
|
||||
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}
|
||||
</small> </label>
|
||||
<input type="number" class="form-control" name="server_port" id="server_port"
|
||||
value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1"
|
||||
required>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }}
|
||||
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc',
|
||||
data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after"
|
||||
value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="form-check-flat">
|
||||
<label for="auto_start" class="form-check-label ml-4 mb-4">
|
||||
{% if data['server_stats']['server_id']['auto_start'] %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked=""
|
||||
value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1">{{
|
||||
translate('serverConfig', 'serverAutoStart', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
<label for="crash_detection" class="form-check-label ml-4 mb-4">
|
||||
{% if data['server_stats']['server_id']['crash_detection'] %}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection"
|
||||
checked="" value="1">{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection"
|
||||
value="1">{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
</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" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig',
|
||||
'cancel', data['lang']) }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{ translate('serverConfigHelp', 'title', data['lang']) }}</h4>
|
||||
<p class="card-description"> {{ translate('serverConfigHelp', 'desc', data['lang']) }}</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
{% raw translate('serverConfigHelp', 'perms', data['lang']) %}
|
||||
</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% if data['server_stats']['running'] %}
|
||||
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
|
||||
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
|
||||
'update', data['lang']) }}</button>
|
||||
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang'])
|
||||
}}</a><br />
|
||||
<small>{{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }}</small>
|
||||
{% else %}
|
||||
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
|
||||
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
|
||||
'update', data['lang']) }}</button>
|
||||
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig',
|
||||
'deleteServer', data['lang']) }}</button>
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -161,147 +221,156 @@
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
const serverId = new URLSearchParams(document.location.search).get('id')
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("ready!");
|
||||
|
||||
});
|
||||
|
||||
function deleteServerE(callback) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_server?id=' + serverId,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
},
|
||||
});
|
||||
|
||||
function deleteServerE(callback) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/delete_server?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
data: {
|
||||
},
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
function deleteServerFilesE(path, callback) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/delete_server_files?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
data: {
|
||||
},
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
|
||||
|
||||
function send_command (server_id, command){
|
||||
<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){ location.reload(); }, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
if(command != "delete_server" && command != "delete_server_files"){
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingRequest", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientUpdate", data['lang']) %} </div>'
|
||||
}
|
||||
function deleteServerFilesE(path, callback) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/ajax/delete_server_files?id=' + serverId,
|
||||
data: {
|
||||
},
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function send_command(serverId, command) {
|
||||
//<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + serverId,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function () { location.reload(); }, 10000);
|
||||
}
|
||||
});
|
||||
if (command != "delete_server" && command != "delete_server_files") {
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingRequest", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientUpdate", data['lang']) %} </div>'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteServer (){
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteFilesQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverConfig', 'yesDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-link',
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result){
|
||||
deleteServerE()
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
|
||||
return;}
|
||||
else{
|
||||
deleteServerFilesE();
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
}
|
||||
function deleteServer() {
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.dialog({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteFilesQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
files: {
|
||||
label: "{{ translate('serverConfig', 'yesDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-danger',
|
||||
callback: function () {
|
||||
deleteServerFilesE();
|
||||
setTimeout(function () { window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
function deleteConfirm (){
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteServerQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverConfig', 'yesDelete', data['lang']) }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDelete', data['lang']) }}",
|
||||
className: 'btn-link',
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result){
|
||||
return;
|
||||
return;}
|
||||
else{
|
||||
deleteServer();
|
||||
}
|
||||
|
||||
},
|
||||
noFiles: {
|
||||
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-outline-danger',
|
||||
callback: function () {
|
||||
deleteServerE()
|
||||
setTimeout(function () { window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'cancel', data['lang']) }}",
|
||||
className: 'btn-secondary',
|
||||
callback: function () {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
function deleteConfirm() {
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteServerQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverConfig', 'yesDelete', data['lang']) }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDelete', data['lang']) }}",
|
||||
className: 'btn-link',
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (!result) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
deleteServer();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
@ -25,14 +24,14 @@
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
{% include "parts/details_stats.html" %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
{% include "parts/server_controls_list.html" %}
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="input-group">
|
||||
@ -56,42 +55,44 @@
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
function get_server_log(){
|
||||
if( !$("#stop_scroll").is(':checked')){
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/ajax/server_log?id={{ data['server_stats']['server_id']['server_id'] }}&full=1',
|
||||
dataType: 'text',
|
||||
success: function (data) {
|
||||
console.log('Got Log From Server')
|
||||
$('#virt_console').html(data);
|
||||
scroll();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const serverId = new URLSearchParams(document.location.search).get('id')
|
||||
function get_server_log(){
|
||||
if( !$("#stop_scroll").is(':checked')){
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/ajax/server_log?id=' + serverId + '&full=1',
|
||||
dataType: 'text',
|
||||
success: function (data) {
|
||||
console.log('Got Log From Server')
|
||||
$('#virt_console').html(data);
|
||||
scroll();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
get_server_log()
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
get_server_log()
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function scroll(){
|
||||
var logview = $('#virt_console');
|
||||
if(logview.length)
|
||||
logview.scrollTop(logview[0].scrollHeight - logview.height());
|
||||
}
|
||||
function scroll(){
|
||||
var logview = $('#virt_console');
|
||||
if(logview.length)
|
||||
logview.scrollTop(logview[0].scrollHeight - logview.height());
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
267
app/frontend/templates/panel/server_schedule_edit.html
Normal file
267
app/frontend/templates/panel/server_schedule_edit.html
Normal file
@ -0,0 +1,267 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html" %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html" %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-sm-8">
|
||||
{% if data['new_schedule'] == True %}
|
||||
<form class="forms-sample" method="post" action="/panel/new_schedule?id={{ data['server_stats']['server_id']['server_id'] }}">
|
||||
{% else %}
|
||||
<form class="forms-sample" method="post" action="/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['schedule']['schedule_id'] }}">
|
||||
{% end %}
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="difficulty">Basic / Cron / Chain-Reaction Select<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']) }}</option>
|
||||
<option id="reaction" value="reaction">{{ translate('serverScheduleConfig', 'reaction' , data['lang']) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="server_name">Action<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' , data['lang']) }}</option>
|
||||
<option id="stop" value="stop">{{ translate('serverScheduleConfig', 'stop' , data['lang']) }}</option>
|
||||
<option id="backup" value="backup">{{ translate('serverScheduleConfig', 'backup' , data['lang']) }}</option>
|
||||
<option id="command" value="command">{{ translate('serverScheduleConfig', 'custom' , data['lang']) }}</option>
|
||||
</select>
|
||||
</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' , data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="interval" id="interval" value="{{ data['schedule']['interval'] }}" placeholder="Interval" required>
|
||||
<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'] }}">
|
||||
<option id = "days" value="days">{{ translate('serverScheduleConfig', 'days' , data['lang']) }}</option>
|
||||
<option id = "hours" value="hours">{{ translate('serverScheduleConfig', 'hours' , data['lang']) }}</option>
|
||||
<option id = "minutes" value="minutes">{{ translate('serverScheduleConfig', 'minutes' , data['lang']) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="ifDays" style="display: block;">
|
||||
<div class="form-group">
|
||||
<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="time" id="time" value="{{ data['schedule']['time'] }}" placeholder="Time" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ifYes" style="display: none;">
|
||||
<div class="form-group">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ifAdvanced" style="display: 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']) }}</small> </label>
|
||||
<input type="input" class="form-control" name="cron" id="cron" value="{{ data['schedule']['cron_string'] }}" placeholder="* * * * *">
|
||||
</div>
|
||||
</div>
|
||||
<div id="ifReaction" style="display: none;">
|
||||
<div class="form-group">
|
||||
<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="0">
|
||||
<br>
|
||||
<br>
|
||||
<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'] }}">
|
||||
{% for schedule in data['schedules'] %}
|
||||
{% if schedule.schedule_id != data['schedule']['schedule_id'] %}
|
||||
{% if schedule.interval != '' %}
|
||||
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">ID: {{schedule.schedule_id}} | {{schedule.command}} | {{schedule.interval}} {{ schedule.interval_type}}</option>
|
||||
{% else %}
|
||||
<option id="{{schedule.schedule_id}}" value="{{schedule.schedule_id}}">ID: {{schedule.schedule_id}} {{schedule.command}} {{schedule.cron_string}}</option>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% end %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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']) }}
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<div class="form-check-flat">
|
||||
<label for="one_time" class="form-check-label ml-4 mb-4">
|
||||
<input type="checkbox" class="form-check-input" id="one_time" name="one_time" value="1">{{ translate('serverScheduleConfig', 'one-time' , data['lang']) }}
|
||||
</label>
|
||||
|
||||
</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', data['lang']) }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-4 grid-margin">
|
||||
<h4>{{ translate('serverScheduleConfig', 'children' , data['lang']) }}</h4>
|
||||
<ul>
|
||||
{% 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.schedule_id}} | {{schedule.command}}</a></li>
|
||||
{% end %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
|
||||
});
|
||||
|
||||
function yesnoCheck() {
|
||||
if (document.getElementById('action').value == "command") {
|
||||
document.getElementById("ifYes").style.display = "block";
|
||||
document.getElementById("command_input").required = true;
|
||||
} else {
|
||||
document.getElementById("ifYes").style.display = "none";
|
||||
document.getElementById("command_input").required = false;
|
||||
}
|
||||
}
|
||||
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("delay").required = false;
|
||||
document.getElementById("parent").required = false;
|
||||
document.getElementById("interval").required = false;
|
||||
document.getElementById("time").required = false;
|
||||
} 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("delay").required = true;
|
||||
document.getElementById("parent").required = true;
|
||||
document.getElementById("interval").required = false;
|
||||
document.getElementById("time").required = false;
|
||||
}
|
||||
else {
|
||||
document.getElementById("ifAdvanced").style.display = "none";
|
||||
document.getElementById("ifReaction").style.display = "none";
|
||||
document.getElementById("ifBasic").style.display = "block";
|
||||
document.getElementById("delay").required = false;
|
||||
document.getElementById("parent").required = false;
|
||||
document.getElementById("interval").required = true;
|
||||
document.getElementById("time").required = true;
|
||||
}
|
||||
}
|
||||
function ifDays() {
|
||||
if (document.getElementById('interval_type').value == "days") {
|
||||
document.getElementById("ifDays").style.display = "block";
|
||||
document.getElementById("time").required = true;
|
||||
} else {
|
||||
document.getElementById("ifDays").style.display = "none";
|
||||
document.getElementById("time").required = false;
|
||||
}
|
||||
}
|
||||
|
||||
function del_task(sch_id, id){
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id,
|
||||
data: {
|
||||
schedule_id: sch_id,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
function startup(){
|
||||
try{
|
||||
document.getElementById("{{ data['schedule']['interval_type'] }}").setAttribute('selected', true);
|
||||
}catch{
|
||||
console.log("no element named")
|
||||
}
|
||||
try{
|
||||
document.getElementById("{{ data['schedule']['difficulty'] }}").setAttribute('selected', true);
|
||||
}catch{
|
||||
console.log("no element named")
|
||||
}
|
||||
try{
|
||||
document.getElementById("{{ data['schedule']['action'] }}").setAttribute('selected', true);
|
||||
|
||||
}catch{
|
||||
console.log("no element named")
|
||||
}
|
||||
ifDays();
|
||||
yesnoCheck();
|
||||
basicAdvanced();
|
||||
if("{{ data['schedule']['enabled'] }}" == 'True'){
|
||||
document.getElementById('enabled').checked = true;
|
||||
}else{
|
||||
document.getElementById('enabled').checked = false;
|
||||
}
|
||||
if("{{ data['schedule']['one_time'] }}" == 'True'){
|
||||
document.getElementById('one_time').checked = true;
|
||||
}else{
|
||||
document.getElementById('one_time').checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload(startup())
|
||||
</script>
|
||||
|
||||
{% end %}
|
374
app/frontend/templates/panel/server_schedules.html
Normal file
374
app/frontend/templates/panel/server_schedules.html
Normal file
@ -0,0 +1,374 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12" style="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> Scheduled Tasks</h4>
|
||||
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}", data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}", data-placement="bottom"></span>
|
||||
<div><button onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`" class="btn btn-info">Create New Schedule <i class="fas fa-pencil-alt"></i></button></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" style="table-layout:fixed;">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th style="width: 2%; min-width: 10px;">ID</th>
|
||||
<th style="width: 23%; min-width: 50px;">Action</th>
|
||||
<th style="width: 40%; min-width: 50px;">Command</th>
|
||||
<th style="width: 10%; min-width: 50px;">Interval</th>
|
||||
<th style="width: 10%; min-width: 50px;">Start Time</th>
|
||||
<th style="width: 10%; min-width: 50px;">Enabled</th>
|
||||
<th style="width: 10%; min-width: 50px;">Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for schedule in data['schedules'] %}
|
||||
<tr>
|
||||
<td id="{{schedule.schedule_id}}" class="id">
|
||||
<p>{{schedule.schedule_id}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.action}}" class="action">
|
||||
<p>{{schedule.action}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
|
||||
<p>{{schedule.command}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.interval}}" class="action">
|
||||
{% if schedule.interval != '' %}
|
||||
<p>Every</p>
|
||||
<p>{{schedule.interval}} {{schedule.interval_type}}</p>
|
||||
{% elif schedule.interval_type == 'reaction' %}
|
||||
<p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p>
|
||||
{% else %}
|
||||
<p>Cron String:</p>
|
||||
<p>{{schedule.cron_string}}</p>
|
||||
{% end %}
|
||||
</td>
|
||||
<td id="{{schedule.start_time}}" class="action">
|
||||
<p>{{schedule.start_time}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.enabled}}" class="action">
|
||||
{% if schedule.enabled %}
|
||||
<span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
</td>
|
||||
<td id="{{schedule.action}}" class="action">
|
||||
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
|
||||
<i class="fas fa-pencil-alt"></i>
|
||||
</button>
|
||||
<br>
|
||||
<br>
|
||||
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" style="table-layout:fixed;">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th style="width: 25%; min-width: 50px;">Action</th>
|
||||
<th style="max-width: 40%; min-width: 50px;">Command</th>
|
||||
<th style="width: 10%; min-width: 50px;">Enabled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for schedule in data['schedules'] %}
|
||||
<tr data-toggle="modal" data-target="#task_details_{{schedule.schedule_id}}">
|
||||
<td id="{{schedule.action}}" class="action">
|
||||
<p>{{schedule.action}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
|
||||
<p>{{schedule.command}}</p>
|
||||
</td>
|
||||
<td id="{{schedule.enabled}}" class="action">
|
||||
{% if schedule.enabled %}
|
||||
<span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Task Details</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul style="list-style: none;">
|
||||
<li id="{{schedule.schedule_id}}" class="id" style="border-top: .1em solid gray;">
|
||||
<h4>ID</h4><p>{{schedule.schedule_id}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.action}}" class="action" style="border-top: .1em solid gray;">
|
||||
<h4>Action</h4><p>{{schedule.action}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.command}}" class="action" style="border-top: .1em solid gray;">
|
||||
<h4>Command</h4><p>{{schedule.command}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.interval}}" class="action" style="border-top: .1em solid gray;">
|
||||
{% if schedule.interval != '' %}
|
||||
<h4>Interval</h4> <p>Every {{schedule.interval}} {{schedule.interval_type}}</p>
|
||||
{% elif schedule.interval_type == 'reaction' %}
|
||||
<h4>Interval</h4> <p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p>
|
||||
{% else %}
|
||||
<h4>Interval</h4> <p>Cron String: {{schedule.cron_string}}</p>
|
||||
{% end %}
|
||||
</li>
|
||||
<li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;">
|
||||
<h4>Start Time</h4> <p>{{schedule.start_time}}</p>
|
||||
</li>
|
||||
<li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
|
||||
{% if schedule.enabled %}
|
||||
<h4>Enabled</h4> <span class="text-success">
|
||||
<i class="fas fa-check-square"></i> Yes
|
||||
</span>
|
||||
{% else %}
|
||||
<h4>Enabled</h4> <span class="text-danger">
|
||||
<i class="far fa-times-square"></i> No
|
||||
</span>
|
||||
{% end %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="window.location.href='/panel/edit_schedule?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{schedule.schedule_id}}'" class="btn btn-info">
|
||||
<i class="fas fa-pencil-alt"></i> Edit
|
||||
</button>
|
||||
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i> Delete
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.popover-body{
|
||||
color: white !important;;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<style>
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
td::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
td {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
</style>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
const serverId = new URLSearchParams(document.location.search).get('id')
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
$('#schedule_table').DataTable({
|
||||
'order': [4, 'desc']
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
$('#mini_schedule_table').DataTable({
|
||||
'order': [2, 'desc']
|
||||
}
|
||||
);
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = true;
|
||||
});
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
document.getElementById('schedule_table_wrapper').hidden = true;
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = false;
|
||||
}
|
||||
|
||||
});
|
||||
$(window).ready(function(){
|
||||
$('body').click(function(){
|
||||
$('.too_small').popover("hide");
|
||||
});
|
||||
});
|
||||
$(window).resize(function() {
|
||||
// This will execute whenever the window is resized
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
document.getElementById('schedule_table_wrapper').hidden = true;
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = false;
|
||||
}
|
||||
else{
|
||||
$('.too_small').popover("hide");
|
||||
document.getElementById('schedule_table_wrapper').hidden = false;
|
||||
document.getElementById('mini_schedule_table_wrapper').hidden = true;
|
||||
} // New width
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
|
||||
});
|
||||
|
||||
function yesnoCheck(that) {
|
||||
if (that.value == "command") {
|
||||
document.getElementById("ifYes").style.display = "block";
|
||||
document.getElementById("command").required = true;
|
||||
} else {
|
||||
document.getElementById("ifYes").style.display = "none";
|
||||
document.getElementById("command").required = false;
|
||||
}
|
||||
}
|
||||
function basicAdvanced(that) {
|
||||
if (that.value == "advanced") {
|
||||
document.getElementById("ifAdvanced").style.display = "block";
|
||||
document.getElementById("ifBasic").style.display = "none";
|
||||
document.getElementById("interval").required = false;
|
||||
document.getElementById("time").required = false;
|
||||
} else {
|
||||
document.getElementById("ifAdvanced").style.display = "none";
|
||||
document.getElementById("ifBasic").style.display = "block";
|
||||
document.getElementById("interval").required = true;
|
||||
document.getElementById("time").required = true;
|
||||
}
|
||||
}
|
||||
function ifDays(that) {
|
||||
if (that.value == "days") {
|
||||
document.getElementById("ifDays").style.display = "block";
|
||||
document.getElementById("time").required = true;
|
||||
} else {
|
||||
document.getElementById("ifDays").style.display = "none";
|
||||
document.getElementById("time").required = false;
|
||||
}
|
||||
}
|
||||
|
||||
$( ".del_button" ).click(function() {
|
||||
var sch_id = $(this).data('sch');
|
||||
|
||||
console.log(sch_id)
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{{ translate('serverSchedules', 'areYouSure', data['lang']) }}",
|
||||
message: "{{ translate('serverSchedules', 'confirmDelete', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverSchedules", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
className: 'btn-outline-danger',
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverSchedules", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
del_task(sch_id, serverId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function del_task(sch_id, id){
|
||||
var token = getCookie("_xsrf")
|
||||
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id,
|
||||
data: {
|
||||
schedule_id: sch_id,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -1,7 +1,6 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
@ -10,14 +9,14 @@
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Page Title Header Starts-->
|
||||
<!-- Page Title Header Starts-->
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -25,67 +24,74 @@
|
||||
</div>
|
||||
<!-- Page Title Header Ends-->
|
||||
|
||||
{% include "parts/details_stats.html %}
|
||||
{% include "parts/details_stats.html %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<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 #383e5d; background-color:#2a2c44;height:500px; overflow: scroll;"></div>
|
||||
</div>
|
||||
<br />
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<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="">
|
||||
<span class="input-group-btn ml-5">
|
||||
<input type="hidden" value="" id="last_command"/>
|
||||
<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['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">{{ translate('serverTerm', 'updating', data['lang']) }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% elif data['waiting_start'] %}
|
||||
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', 'starting', data['lang']) }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% 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(server_id, '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(server_id, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="send_command(server_id, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% end %}
|
||||
{% end %}
|
||||
<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 #383e5d; background-color:#2a2c44;height:500px; overflow: scroll;"></div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style="gap: 0.5rem;" class="input-group flex-wrap">
|
||||
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" autofocus="">
|
||||
<span class="input-group-btn ml-5">
|
||||
<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['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">{{ translate('serverTerm', 'updating', data['lang']) }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% elif data['waiting_start'] %}
|
||||
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', 'starting', data['lang']) }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% elif data['downloading'] %}
|
||||
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'downloading',
|
||||
data['lang']) }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="send_command(serverId, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
|
||||
<button onclick="send_command(serverId, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="send_command(serverId, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 */
|
||||
}
|
||||
#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 */
|
||||
}
|
||||
</style>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
@ -94,156 +100,192 @@
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
function send_command (server_id, command){
|
||||
if (command == 'start_server'){
|
||||
startBtn.setAttribute('disabled', 'disabled');
|
||||
restartBtn.removeAttribute('disabled');
|
||||
stopBtn.removeAttribute('disabled');
|
||||
}
|
||||
if (command == 'stop_server'){
|
||||
startBtn.removeAttribute('disabled');
|
||||
restartBtn.setAttribute('disabled', 'disabled');
|
||||
stopBtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
const serverId = new URLSearchParams(document.location.search).get('id')
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){
|
||||
if (command != 'start_server'){
|
||||
location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
if (updateButton.isUpdating){
|
||||
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>';
|
||||
}
|
||||
else{
|
||||
window.location.reload()
|
||||
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "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(server_id, "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>';
|
||||
}
|
||||
});
|
||||
}
|
||||
// Convert running to lower case (example: 'True' converts to 'true') and
|
||||
// then to boolean via JSON.parse()
|
||||
let online = JSON.parse('{{ data['server_stats']['running'] }}'.toLowerCase());
|
||||
|
||||
let startBtn = document.querySelector('#start-btn');
|
||||
let restartBtn = document.querySelector('#restart-btn');
|
||||
let stopBtn = document.querySelector('#stop-btn');
|
||||
|
||||
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
||||
if (online) {
|
||||
function send_command(serverId, command) {
|
||||
if (command == 'start_server') {
|
||||
startBtn.setAttribute('disabled', 'disabled');
|
||||
restartBtn.removeAttribute('disabled');
|
||||
stopBtn.removeAttribute('disabled');
|
||||
} else {
|
||||
}
|
||||
if (command == 'stop_server') {
|
||||
startBtn.removeAttribute('disabled');
|
||||
restartBtn.setAttribute('disabled', 'disabled');
|
||||
stopBtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
{% end %}
|
||||
//<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
|
||||
|
||||
function get_server_log(){
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/ajax/server_log?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
dataType: 'text',
|
||||
success: function (data) {
|
||||
console.log('Got Log From Server')
|
||||
$('#virt_console').html(data);
|
||||
scrollConsole();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function new_line_handler(data) {
|
||||
$('#virt_console').append(data.line)
|
||||
const elem = document.getElementById('virt_console');
|
||||
const scrollDiff = (elem.scrollHeight - elem.scrollTop) - elem.clientHeight;
|
||||
if (!$("#stop_scroll").is(':checked') && scrollDiff < 450) {
|
||||
scrollConsole()
|
||||
}
|
||||
}
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log( "ready!" );
|
||||
get_server_log()
|
||||
|
||||
webSocket.on('vterm_new_line', new_line_handler)
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: { 'X-XSRFToken': token },
|
||||
url: '/server/command?command=' + command + '&id=' + serverId,
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
}
|
||||
});
|
||||
|
||||
$('#server_command').on('keydown', function (e) {
|
||||
if (e.which == 13){
|
||||
$(this).attr("disabled", "disabled"); //Disable textbox to prevent multiple submit
|
||||
send_command_to_server()
|
||||
$(this).removeAttr("disabled"); //Enable the textbox again if needed.
|
||||
$(this).focus();
|
||||
}
|
||||
else if (e.which == 38){
|
||||
last_command = $('#last_command').val()
|
||||
$("#server_command").val(last_command)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$("#submit").click(function(e) {
|
||||
e.preventDefault();
|
||||
send_command_to_server();
|
||||
|
||||
}
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
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>';
|
||||
}
|
||||
}
|
||||
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>';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Convert running to lower case (example: 'True' converts to 'true') and
|
||||
// then to boolean via JSON.parse()
|
||||
let online = JSON.parse("{{ data['server_stats']['running'] }}".toLowerCase());
|
||||
|
||||
function scrollConsole(){
|
||||
var logview = $('#virt_console');
|
||||
if(logview.length)
|
||||
logview.scrollTop(logview[0].scrollHeight - logview.height());
|
||||
let startBtn = document.querySelector('#start-btn');
|
||||
let restartBtn = document.querySelector('#restart-btn');
|
||||
let stopBtn = document.querySelector('#stop-btn');
|
||||
|
||||
//{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
||||
if (online) {
|
||||
startBtn.setAttribute('disabled', 'disabled');
|
||||
restartBtn.removeAttribute('disabled');
|
||||
stopBtn.removeAttribute('disabled');
|
||||
} else {
|
||||
startBtn.removeAttribute('disabled');
|
||||
restartBtn.setAttribute('disabled', 'disabled');
|
||||
stopBtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_reload', function () {
|
||||
location.reload()
|
||||
});
|
||||
}
|
||||
//{% end %}
|
||||
|
||||
function get_server_log() {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/ajax/server_log?id=' + serverId,
|
||||
dataType: 'text',
|
||||
success: function (data) {
|
||||
console.log('Got Log From Server')
|
||||
$('#virt_console').html(data);
|
||||
scrollConsole();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function new_line_handler(data) {
|
||||
$('#virt_console').append(data.line)
|
||||
const elem = document.getElementById('virt_console');
|
||||
const scrollDiff = (elem.scrollHeight - elem.scrollTop) - elem.clientHeight;
|
||||
if (!$("#stop_scroll").is(':checked') && scrollDiff < 450) {
|
||||
scrollConsole()
|
||||
}
|
||||
}
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("ready!");
|
||||
get_server_log()
|
||||
|
||||
webSocket.on('vterm_new_line', new_line_handler)
|
||||
});
|
||||
|
||||
$('#server_command').on('keydown', function (e) {
|
||||
if (e.which == 13) {
|
||||
$(this).attr("disabled", "disabled"); //Disable textbox to prevent multiple submit
|
||||
sendConsoleCommand()
|
||||
$(this).removeAttr("disabled"); //Enable the textbox again if needed.
|
||||
$(this).focus();
|
||||
}
|
||||
else if (e.which == 38) {
|
||||
e.preventDefault();
|
||||
$('#server_command').val(cmdHistory.getPrev());
|
||||
} else if (e.which == 40) {
|
||||
e.preventDefault();
|
||||
$('#server_command').val(cmdHistory.getNext());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function send_command_to_server(){
|
||||
var server_command = $("#server_command").val()
|
||||
console.log(server_command)
|
||||
$("#last_command").val(server_command)
|
||||
$("#submit").click(function (e) {
|
||||
e.preventDefault();
|
||||
sendConsoleCommand();
|
||||
|
||||
var token = getCookie("_xsrf")
|
||||
});
|
||||
|
||||
data_to_send = { command :server_command, }
|
||||
function scrollConsole() {
|
||||
var logview = $('#virt_console');
|
||||
if (logview.length)
|
||||
logview.scrollTop(logview[0].scrollHeight - logview.height());
|
||||
}
|
||||
|
||||
console.log('sending command: ' + server_command)
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/send_command?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
data: data_to_send,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
$("#server_command").val('')
|
||||
},
|
||||
});
|
||||
|
||||
async function sendConsoleCommand() {
|
||||
let serverCommand = $("#server_command").val()
|
||||
console.log(serverCommand)
|
||||
|
||||
cmdHistory.push(serverCommand);
|
||||
|
||||
let token = getCookie("_xsrf")
|
||||
|
||||
let formdata = new FormData();
|
||||
|
||||
formdata.append('command', serverCommand)
|
||||
|
||||
console.log('sending command: ' + serverCommand)
|
||||
let res = await fetch("/ajax/send_command?id=" + serverId, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: formdata,
|
||||
});
|
||||
|
||||
let responseData = await res.text();
|
||||
console.log("got response:");
|
||||
console.log(responseData);
|
||||
$("#server_command").val('')
|
||||
}
|
||||
|
||||
|
||||
const cmdHistory = {
|
||||
history: [],
|
||||
current: 0,
|
||||
push: function (cmd) {
|
||||
this.history.push(cmd);
|
||||
this.current = this.history.length - 1;
|
||||
},
|
||||
getPrev: function () {
|
||||
const prevCommand = this.history[this.current];
|
||||
this.current--;
|
||||
if (this.current < 0) this.current = 0;
|
||||
return prevCommand;
|
||||
},
|
||||
getNext: function () {
|
||||
this.current++;
|
||||
if (this.current > (this.history.length - 1)) {
|
||||
this.current = (this.history.length - 1);
|
||||
return '';
|
||||
}
|
||||
const nextCommand = this.history[this.current];
|
||||
return nextCommand;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
@ -17,7 +17,8 @@
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
<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">
|
||||
<div class="container-scroller">
|
||||
@ -28,7 +29,7 @@
|
||||
|
||||
<div class="auto-form-wrapper">
|
||||
<div class="text-center">
|
||||
<img src="/static/assets/images/logo_long.jpg"><br /><br />
|
||||
<img src="/static/assets/images/logo_long.svg"><br /><br />
|
||||
<div class="col-sm-12 grid-margin stretch-card">
|
||||
<div class="card card-statistics social-card facebook-card card-colored">
|
||||
<div class="card-body">
|
||||
|
@ -17,7 +17,8 @@
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
<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">
|
||||
<div class="container-scroller">
|
||||
@ -28,7 +29,7 @@
|
||||
|
||||
<div class="auto-form-wrapper">
|
||||
<div class="text-center">
|
||||
<img src="/static/assets/images/logo_long.jpg"><br /><br />
|
||||
<img src="/static/assets/images/logo_long.svg"><br /><br />
|
||||
<div class="col-sm-12 grid-margin stretch-card">
|
||||
<div class="card card-statistics social-card google-card card-colored">
|
||||
<div class="card-body">
|
||||
|
@ -17,7 +17,8 @@
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
<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">
|
||||
<div class="container-scroller">
|
||||
@ -28,7 +29,7 @@
|
||||
|
||||
<div class="auto-form-wrapper login-modal">
|
||||
<div class="text-center">
|
||||
<img src="/static/assets/images/logo_long.jpg">
|
||||
<img src="/static/assets/images/logo_long.svg">
|
||||
</div>
|
||||
<style>
|
||||
.login-modal {
|
||||
@ -71,6 +72,11 @@
|
||||
<div class="form-group">
|
||||
<button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login', data['lang']) }}</button>
|
||||
</div>
|
||||
{% if error_msg is not None %}
|
||||
<fieldset style="color: red; text-align: center;">
|
||||
<span>{{error_msg}}</span>
|
||||
</fieldset>
|
||||
{% end %}
|
||||
<div class="form-group d-flex justify-content-between">
|
||||
<div class="form-check form-check-flat mt-0">
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends ../public_base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<meta http-equiv="refresh" content="30">
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard', data['lang']) }}{% end %}
|
||||
@ -22,41 +21,50 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if data['running'] != 0 %}
|
||||
<span id="sync" style="margin-left: 5px;"><i class="fas fa-sync fa-spin"></i></span></h4>
|
||||
{% end %}
|
||||
{% for server in data['servers'] %}
|
||||
<tr>
|
||||
<td>
|
||||
<td id="server_name_{{ server['stats']['server_id']['server_id'] }}">
|
||||
<i class="fas fa-server"></i>
|
||||
{{ server['server_data']['server_name'] }}
|
||||
{{ server['server_data']['server_name'] }}
|
||||
</td>
|
||||
{% if server['stats']['int_ping_results'] != 'False' %}
|
||||
<td>
|
||||
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max', data['lang']) }}<br />
|
||||
</td>
|
||||
<td>
|
||||
{% if server['stats']['desc'] != 'False' %}
|
||||
{% if server['raw_ping_result']['icon'] %}
|
||||
<img src="data:image/png;base64,{% raw server['raw_ping_result']['icon'] %}" alt="icon"/>
|
||||
{% else %}
|
||||
<img src="/static/assets/images/pack.png" alt="icon" />
|
||||
{% end %}
|
||||
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{ server['stats']['desc'] }}</span> <br />
|
||||
{% end %}
|
||||
</td>
|
||||
<td>
|
||||
{% if server['stats']['version'] != 'False' %}
|
||||
{{ server['stats']['version'] }}
|
||||
{% end %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td colspan="3">
|
||||
<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Crafty can't get infos from this Server </span>
|
||||
{% if server['stats']['int_ping_results'] != 'False' %}
|
||||
<td id="server_players_{{ server['stats']['server_id']['server_id'] }}">
|
||||
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
|
||||
data['lang']) }}<br />
|
||||
</td>
|
||||
<td id="server_motd_{{ server['stats']['server_id']['server_id'] }}">
|
||||
{% if server['stats']['desc'] != 'False' %}
|
||||
<img src="/static/assets/images/pack.png" alt="icon" style="-webkit-filter:grayscale(100%); filter:grayscale(100%)"/>
|
||||
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{
|
||||
server['stats']['desc'] }}</span> <br />
|
||||
{% end %}
|
||||
<td>
|
||||
</td>
|
||||
<td id="server_version_{{ server['stats']['server_id']['server_id'] }}">
|
||||
{% if server['stats']['version'] != 'False' %}
|
||||
{{ server['stats']['version'] }}
|
||||
{% end %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td id="server_players_{{ server['stats']['server_id']['server_id'] }}">
|
||||
<span class="text-warning"><i class="fas fa-exclamation-triangle"></i></span>
|
||||
</td>
|
||||
<td id="server_motd_{{ server['stats']['server_id']['server_id'] }}">
|
||||
<span class="text-warning">Crafty can't get infos from this Server </span>
|
||||
</td>
|
||||
<td id="server_version_{{ server['stats']['server_id']['server_id'] }}">
|
||||
<span class="text-warning"><i class="fas fa-question"></i></i></span>
|
||||
</td>
|
||||
{% end %}
|
||||
<td id="server_online_status_{{ server['stats']['server_id']['server_id'] }}">
|
||||
{% if server['stats']['running'] %}
|
||||
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang']) }}</span>
|
||||
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])
|
||||
}}</span>
|
||||
{% else %}
|
||||
<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang']) }}</span>
|
||||
<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang'])
|
||||
}}</span>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -72,14 +80,81 @@
|
||||
|
||||
{% block js %}
|
||||
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
|
||||
for (element of all_motds) {
|
||||
initParser(element.id, element.id);
|
||||
};
|
||||
}());
|
||||
</script>
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
function display_motd() {
|
||||
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
|
||||
for (element of all_motds) {
|
||||
initParser(element.id, element.id);
|
||||
};
|
||||
}
|
||||
|
||||
{% end %}
|
||||
function update_one_server_status(server) {
|
||||
server_players = document.getElementById('server_players_' + server.id);
|
||||
server_motd = document.getElementById('server_motd_' + server.id);
|
||||
server_version = document.getElementById('server_version_' + server.id);
|
||||
server_online_status = document.getElementById('server_online_status_' + server.id);
|
||||
|
||||
/* TODO Update each element */
|
||||
if (server.int_ping_results) {
|
||||
document.getElementById('sync').innerHTML='';
|
||||
/* Update Players */
|
||||
if (server.players)
|
||||
{
|
||||
server_players.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
|
||||
}
|
||||
|
||||
/* Update Motd */
|
||||
var motd = "";
|
||||
if (server.desc) {
|
||||
if (server.icon) {
|
||||
motd = `<img src="data:image/png;base64,` + server.icon + `" alt="icon" /> `;
|
||||
}
|
||||
else {
|
||||
motd = `<img src="/static/assets/images/pack.png" alt="icon" /> `;
|
||||
}
|
||||
|
||||
motd = motd + `<span id="input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span> <br />`;
|
||||
server_motd.innerHTML = motd;
|
||||
}
|
||||
|
||||
/* Version */
|
||||
if (server.version)
|
||||
{
|
||||
server_version.innerHTML = server.version
|
||||
}
|
||||
}
|
||||
else {
|
||||
server_players.innerHTML = `<span class="text-warning"><i class="fas fa-exclamation-triangle"></i></span>`;
|
||||
server_motd.innerHTML = `<span class="text-warning">Crafty can't get infos from this Server </span>`;
|
||||
server_version.innerHTML = `<span class="text-warning"><i class="fas fa-question"></i></i></span>`
|
||||
}
|
||||
|
||||
/* Update Online Status */
|
||||
var online_status = "";
|
||||
if (server.running) {
|
||||
online_status = `<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])}}</span>`;
|
||||
}
|
||||
else {
|
||||
online_status = `<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang'])}}</span>`;
|
||||
}
|
||||
server_online_status.innerHTML = online_status;
|
||||
}
|
||||
|
||||
function update_servers_status(data) {
|
||||
console.log(data);
|
||||
update_one_server_status(data[0]);
|
||||
display_motd();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("ready!");
|
||||
|
||||
if (webSocket)
|
||||
{
|
||||
webSocket.on('update_server_status', update_servers_status);
|
||||
}
|
||||
}());
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
|
@ -1,59 +1,118 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
{% block meta %}{% end %}
|
||||
<title>{% block title %}{{ _('Default') }}{% end %}</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
|
||||
<!-- endinject -->
|
||||
<!-- Plugin css for this page -->
|
||||
<!-- End Plugin css for this page -->
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<div class="container-scroller">
|
||||
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
|
||||
<div class="row w-100">
|
||||
<div class="mx-auto">
|
||||
<div class="auto-form-wrapper">
|
||||
{% block content %}
|
||||
{% end %}
|
||||
</div>
|
||||
<html lang="{{ data['lang_page'] }}">
|
||||
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
{% block meta %}{% end %}
|
||||
<title>{% block title %}{{ _('Default') }}{% end %}</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
|
||||
<!-- endinject -->
|
||||
<!-- Plugin css for this page -->
|
||||
<!-- End Plugin css for this page -->
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
|
||||
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
|
||||
</head>
|
||||
|
||||
<body class="dark-theme">
|
||||
<div class="container-scroller">
|
||||
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
|
||||
<div class="row w-100">
|
||||
<div class="mx-auto">
|
||||
<div class="auto-form-wrapper">
|
||||
{% block content %}
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
</div>
|
||||
<!-- page-body-wrapper ends -->
|
||||
<!-- content-wrapper ends -->
|
||||
</div>
|
||||
<!-- container-scroller -->
|
||||
<!-- plugins:js -->
|
||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||
<!-- endinject -->
|
||||
<!-- inject:js -->
|
||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||
<script src="/static/assets/js/shared/misc.js"></script>
|
||||
<script src="/static/assets/js/shared/settings.js"></script>
|
||||
<script src="/static/assets/js/shared/todolist.js"></script>
|
||||
<!-- endinject -->
|
||||
|
||||
{% block js %}
|
||||
<!-- Custom js for this page -->
|
||||
<!-- End custom js for this page -->
|
||||
{% end %}
|
||||
|
||||
</body>
|
||||
<!-- page-body-wrapper ends -->
|
||||
</div>
|
||||
<!-- container-scroller -->
|
||||
<!-- plugins:js -->
|
||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||
<!-- endinject -->
|
||||
<!-- inject:js -->
|
||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||
<script src="/static/assets/js/shared/misc.js"></script>
|
||||
<script src="/static/assets/js/shared/settings.js"></script>
|
||||
<script src="/static/assets/js/shared/todolist.js"></script>
|
||||
<!-- endinject -->
|
||||
<script>
|
||||
|
||||
// {% if request.protocol == 'https' %}
|
||||
let usingWebSockets = true;
|
||||
|
||||
let listenEvents = [];
|
||||
|
||||
try {
|
||||
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
|
||||
page = 'page=' + encodeURIComponent(location.pathname)
|
||||
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
|
||||
wsInternal.onopen = function () {
|
||||
console.log('opened WebSocket connection:', wsInternal)
|
||||
};
|
||||
wsInternal.onmessage = function (rawMessage) {
|
||||
var message = JSON.parse(rawMessage.data);
|
||||
|
||||
console.log('got message: ', message)
|
||||
|
||||
listenEvents
|
||||
.filter(listenedEvent => listenedEvent.event == message.event)
|
||||
.forEach(listenedEvent => listenedEvent.callback(message.data))
|
||||
};
|
||||
wsInternal.onerror = function (errorEvent) {
|
||||
console.error('WebSocket Error', errorEvent);
|
||||
};
|
||||
wsInternal.onclose = function (closeEvent) {
|
||||
console.log('Closed WebSocket', closeEvent);
|
||||
};
|
||||
|
||||
|
||||
webSocket = {
|
||||
on: function (event, callback) {
|
||||
console.log('registered ' + event + ' event');
|
||||
listenEvents.push({ event: event, callback: callback })
|
||||
},
|
||||
emit: function (event, data) {
|
||||
var message = {
|
||||
event: event,
|
||||
data: data
|
||||
}
|
||||
|
||||
wsInternal.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error while making websocket helpers', error);
|
||||
usingWebSockets = false;
|
||||
}
|
||||
// {% else %}
|
||||
let usingWebSockets = false;
|
||||
warn('WebSockets are not supported in Crafty if not using the https protocol')
|
||||
var webSocket;
|
||||
// {% end%}
|
||||
|
||||
</script>
|
||||
{% block js %}
|
||||
<!-- Custom js for this page -->
|
||||
<!-- End custom js for this page -->
|
||||
{% end %}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
468
app/frontend/templates/server/bedrock_wizard.html
Normal file
468
app/frontend/templates/server/bedrock_wizard.html
Normal file
@ -0,0 +1,468 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverWizard', 'newServer', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/server/step1" role="tab" aria-selected="false">
|
||||
<i class="fas fa-file-signature"></i>Minecraft-Java</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link active" href="/server/bedrock_step1" role="tab" aria-selected="false">
|
||||
<i class="fas fa-file-signature"></i>Minecraft-Bedrock</a>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<div class="d-none" id="overlay" onclick="hide(event)"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_jar" name="create_type">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small></small></label>
|
||||
<input type="number" class="form-control" id="port2" name="port" value="19132" step="1" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-2">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-2">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-2" class="collapse" aria-labelledby="Role-2" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_zip" name="create_type">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{ translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
|
||||
<br>
|
||||
<button class="btn btn-primary mr-2" id="root_files_button" type="button">{{ translate('serverWizard', 'clickRoot', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="bedrock_server" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-sm-12">
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small></small></label>
|
||||
<input type="number" class="form-control" id="port3" name="port" value="19132" step="1" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-3">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-3">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12" style="visibility: hidden;">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard', 'selectZipDir', data['lang']) }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
|
||||
<input type="radio" id="main-tree-input" name="root_path" value="" checked>
|
||||
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{{ translate('serverFiles', 'files', data['lang']) }}
|
||||
</span>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverWizard', 'close', data['lang']) }}</button>
|
||||
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="zip_submit" type="submit" title="You must select server root dir first" disabled class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.scroll {
|
||||
max-height: 12em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.menu-btn {
|
||||
font-size: 0.9em;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
.menu {
|
||||
padding-top: 10px;
|
||||
z-index: 200;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
background-color: #2a2c44;
|
||||
}
|
||||
.menu-option {
|
||||
padding: 6px 20px 6px;
|
||||
color: white;
|
||||
}
|
||||
#overlay {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
/* Remove default bullets */
|
||||
.tree-view,
|
||||
.tree-nested {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Style the items */
|
||||
.tree-item,
|
||||
.files-tree-title {
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
}
|
||||
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
.tree-caret .fa-folder {
|
||||
display: inline-block;
|
||||
}
|
||||
.tree-caret .fa-folder-open {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||
.tree-caret-down .fa-folder {
|
||||
display: none;
|
||||
}
|
||||
.tree-caret-down .fa-folder-open {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Hide the nested list */
|
||||
.tree-nested {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js%}
|
||||
<script>
|
||||
document.getElementById("root_files_button").addEventListener("click", function(){
|
||||
if(document.forms["zip"]["server_path"].value != ""){
|
||||
if(document.getElementById('root_files_button').classList.contains('clicked')){
|
||||
document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input" name="root_path" value="" checked><span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate('serverFiles', 'files', data['lang']) }}</span></input>'
|
||||
}else{
|
||||
document.getElementById('root_files_button').classList.add('clicked')
|
||||
}
|
||||
path = document.forms["zip"]["server_path"].value;
|
||||
console.log(document.forms["zip"]["server_path"].value)
|
||||
var token = getCookie("_xsrf");
|
||||
var dialog = bootbox.dialog({
|
||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||
closeButton: false
|
||||
});
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/unzip_server?id=-1&path='+path,
|
||||
});
|
||||
}else{
|
||||
bootbox.alert("You must input a path before selecting this button");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function dropDown(event) {
|
||||
event.target.parentElement.children[1].classList.remove("d-none");
|
||||
document.getElementById("overlay").classList.remove("d-none");
|
||||
}
|
||||
function hide(event) {
|
||||
var items = document.getElementsByClassName('menu');
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].classList.add("d-none");
|
||||
}
|
||||
document.getElementById("overlay").classList.add("d-none");
|
||||
}
|
||||
|
||||
function wait_msg(importing){
|
||||
bootbox.alert({
|
||||
title: importing ? '{% raw translate("serverWizard", "importing", data['lang']) %}' : '{% raw translate("serverWizard", "downloading", data['lang']) %}',
|
||||
message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data['lang']) %}',
|
||||
});
|
||||
}
|
||||
|
||||
function show_file_tree(){
|
||||
$("#dir_select").modal();
|
||||
}
|
||||
|
||||
function getTreeView(path) {
|
||||
document.getElementById('zip_submit').disabled = false;
|
||||
path = path
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_zip_tree?id=-1&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try{
|
||||
document.getElementById('main-tree-div').innerHTML += text;
|
||||
document.getElementById('main-tree').parentElement.classList.add("clicked");
|
||||
}catch{
|
||||
document.getElementById('files-tree').innerHTML = text;
|
||||
}
|
||||
|
||||
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
||||
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getToggleMain(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
document.getElementById("files-tree").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret");
|
||||
}
|
||||
|
||||
|
||||
function getDirView(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
|
||||
if (document.getElementById(path).classList.contains('clicked')){
|
||||
|
||||
var toggler = document.getElementById(path+"span");
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_zip_dir?id=-1&path='+path,
|
||||
dataType: 'text',
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
|
||||
try{
|
||||
document.getElementById(path+"span").classList.add('tree-caret-down');
|
||||
document.getElementById(path).innerHTML += text;
|
||||
document.getElementById(path).classList.add("clicked");
|
||||
}catch{
|
||||
console.log("Bad")
|
||||
}
|
||||
|
||||
var toggler = document.getElementById(path);
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')){
|
||||
document.getElementById(path+"span").addEventListener("click", function caretListener() {
|
||||
document.getElementById(path+"ul").classList.toggle("d-block");
|
||||
document.getElementById(path+"span").classList.toggle("tree-caret-down");
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
if (webSocket) {
|
||||
webSocket.on('send_temp_path', function (data) {
|
||||
setTimeout(function(){
|
||||
var x = document.querySelector('.bootbox');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
document.getElementById('main-tree-input').setAttribute('value', data.path)
|
||||
getTreeView(data.path);
|
||||
show_file_tree();
|
||||
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
// array of possible countries in the same order as they appear in the country selection list
|
||||
|
||||
function decodeHtmlCharCodes(str) {
|
||||
return str.replace(""", "\"");
|
||||
}
|
||||
|
||||
function convertHtmlJsonToJavacriptArray(str) {
|
||||
var result = []
|
||||
str = decodeHtmlCharCodes(str)
|
||||
for(var i in str)
|
||||
result.push([i, str [i]]);
|
||||
return result
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
{% end %}
|
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
||||
|
||||
<div class="auto-form-wrapper">
|
||||
<div class="text-center">
|
||||
<!-- <img src="/static/assets/images/logo_long.jpg">-->
|
||||
<!-- <img src="/static/assets/images/logo_long.svg">-->
|
||||
{{ _('Configure Your Existing Server') }}<br /><br />
|
||||
</div>
|
||||
<form action="/public/login" method="post">
|
||||
|
17
app/migrations/20210915205501_backup_schedule.py
Normal file
17
app/migrations/20210915205501_backup_schedule.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
from app.classes.models.management import Schedules
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.drop_columns('backups', ['schedule_id'])
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.add_columns('backups', schedule_id=peewee.ForeignKeyField(Schedules, backref='backups_schedule'))
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_crash.py
Normal file
16
app/migrations/20210915205501_crash.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('server_stats', crashed=peewee.BooleanField(default=False))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('server_stats', ['crashed'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_cron_task.py
Normal file
16
app/migrations/20210915205501_cron_task.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('schedules', cron_string=peewee.CharField(default=""))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('schedules', ['cron_string'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user