Merge branch 'dev' into 'freddy0-Unraid-Template'

# Conflicts:
#   docker/unraid.xml
This commit is contained in:
Iain Powrie 2022-05-19 21:24:09 +00:00
commit 6637748a59
136 changed files with 25591 additions and 7975 deletions

View File

@ -6,6 +6,7 @@ docker-compose.yml
# git & gitlab related # git & gitlab related
.git/ .git/
.gitlab/
.gitignore .gitignore
.gitlab-ci.yml .gitlab-ci.yml

View File

@ -2,15 +2,25 @@
root = true root = true
[*.{js,py,html}] [*]
charset = utf-8 charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
# end_of_line = lf
[*.py] [*.py]
indent_style = space profile = black
indent_size = 4 # > Handled by Black
# indent_style = space
# indent_size = 4
[*.{js,html}] [*.md]
trim_trailing_whitespace = false
[*.html]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
[*.js]
indent_style = tab
indent_size = 4

7
.gitignore vendored
View File

@ -10,6 +10,7 @@ __pycache__/
.env .env
.venv .venv
default.env
env/ env/
venv/ venv/
ENV/ ENV/
@ -17,8 +18,10 @@ env.bak/
venv.bak/ venv.bak/
.idea/ .idea/
servers/ /servers/
backups/ /backups/
/docker/servers/
/docker/backups/
session.lock session.lock
.header .header
default.json default.json

View File

@ -1,35 +1,76 @@
# Crafty Controller 4.0 - Lint & Build Pipes
# [Maintainer: Zedifus(https://gitlab.com/Zedifus)]
###################################################
# yamllint disable rule:line-length
---
stages: stages:
- test - lint
- prod-deployment - prod-deployment
- dev-deployment - dev-deployment
variables: variables:
DOCKER_HOST: tcp://docker:2376 DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs" DOCKER_TLS_CERTDIR: "/certs"
pylint: yamllint:
stage: test stage: lint
image: python:3.7-slim image: registry.gitlab.com/pipeline-components/yamllint:latest
services:
- name: docker:dind
tags: tags:
- 'docker_testers' - "docker"
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS' - if: "$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS"
when: never
script:
- yamllint .
jsonlint:
stage: lint
image: registry.gitlab.com/pipeline-components/jsonlint:latest
tags:
- "docker"
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: "$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS"
when: never when: never
before_script:
- mkdir -p public/badges public/lint
- echo undefined > public/badges/$CI_JOB_NAME.score
- pip install pylint-gitlab
script: 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)" find . -not -path './.git/*' -name '*.json' -type f -print0 |
parallel --will-cite -k -0 -n1 jsonlint -q
black:
stage: lint
image: registry.gitlab.com/pipeline-components/black:latest
tags:
- "docker"
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: "$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS"
when: never
script:
- black --check --verbose -- .
pylint:
stage: lint
image: registry.gitlab.com/pipeline-components/pylint:latest
tags:
- "docker"
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
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)"
# Removed lint badge generation until public release
artifacts: artifacts:
paths: paths:
- public - public
@ -43,12 +84,18 @@ docker-build-dev:
- name: docker:dind - name: docker:dind
stage: dev-deployment stage: dev-deployment
tags: tags:
- docker - "docker_priv"
rules: rules:
- if: $CI_COMMIT_BRANCH == 'dev' - if: $CI_COMMIT_BRANCH == 'dev'
environment: environment:
name: development name: development
before_script: before_script:
- |
apk --no-cache add jq
MAJOR=$(cat app/config/version.json | jq '.major' )
MINOR=$(cat app/config/version.json | jq '.minor' )
SUB=$(cat app/config/version.json | jq '.sub' )
META=$(cat app/config/version.json | jq -r '.meta' )
- | - |
apk --no-cache add curl apk --no-cache add curl
latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p') latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p')
@ -64,12 +111,18 @@ docker-build-dev:
script: script:
- | - |
tag=":$CI_COMMIT_REF_SLUG" tag=":$CI_COMMIT_REF_SLUG"
VERSION="${MAJOR}.${MINOR}.${SUB}-${META}"
- |
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag" echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
echo "Crafty Version: $VERSION"
- docker context create tls-environment - docker context create tls-environment
- docker buildx create --name zedBuilder --use tls-environment - docker buildx create --name zedBuilder --use tls-environment
- docker buildx build - docker buildx build
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}" --cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
--build-arg BUILDKIT_INLINE_CACHE=1 --build-arg BUILDKIT_INLINE_CACHE=1
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}"
--tag "$CI_REGISTRY_IMAGE${tag}" --tag "$CI_REGISTRY_IMAGE${tag}"
--platform linux/arm64/v8,linux/amd64 --platform linux/arm64/v8,linux/amd64
--push . --push .
@ -86,12 +139,18 @@ docker-build-prod:
- name: docker:dind - name: docker:dind
stage: prod-deployment stage: prod-deployment
tags: tags:
- docker - "docker_priv"
rules: rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
environment: environment:
name: production name: production
before_script: before_script:
- |
apk --no-cache add jq
MAJOR=$(cat app/config/version.json | jq '.major' )
MINOR=$(cat app/config/version.json | jq '.minor' )
SUB=$(cat app/config/version.json | jq '.sub' )
META=$(cat app/config/version.json | jq -r '.meta' )
- | - |
apk --no-cache add curl apk --no-cache add curl
latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p') latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p')
@ -107,12 +166,18 @@ docker-build-prod:
script: script:
- | - |
tag="" tag=""
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'" VERSION="${MAJOR}.${MINOR}.${SUB}-${META}"
- |
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
echo "Crafty Version: $VERSION"
- docker context create tls-environment - docker context create tls-environment
- docker buildx create --name zedBuilder --use tls-environment - docker buildx create --name zedBuilder --use tls-environment
- docker buildx build - docker buildx build
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}" --cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
--build-arg BUILDKIT_INLINE_CACHE=1 --build-arg BUILDKIT_INLINE_CACHE=1
--build-arg "BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ")"
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}"
--tag "$CI_REGISTRY_IMAGE${tag}" --tag "$CI_REGISTRY_IMAGE${tag}"
--platform linux/arm64/v8,linux/amd64 --platform linux/arm64/v8,linux/amd64
--push . --push .
@ -126,81 +191,81 @@ docker-build-prod:
win-dev-build: win-dev-build:
stage: dev-deployment stage: dev-deployment
tags: tags:
- win64 - win64
cache: cache:
paths: paths:
- .venv/ - .venv/
rules: rules:
- if: "$CI_COMMIT_BRANCH == 'dev'" - if: "$CI_COMMIT_BRANCH == 'dev'"
environment: environment:
name: development name: development
script: script:
- | - |
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
py -m venv .venv py -m venv .venv
.venv\Scripts\activate.ps1 .venv\Scripts\activate.ps1
pip install pyinstaller pip install pyinstaller
pip install -r requirements.txt pip install -r requirements.txt
- pyinstaller -F main.py - pyinstaller -F main.py
--distpath . --distpath .
--icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico --icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico
--name "crafty_commander" --name "crafty_commander"
--paths .venv\Lib\site-packages --paths .venv\Lib\site-packages
--hidden-import cryptography --hidden-import cryptography
--hidden-import cffi --hidden-import cffi
--hidden-import apscheduler --hidden-import apscheduler
--collect-all tzlocal --collect-all tzlocal
--collect-all tzdata --collect-all tzdata
--collect-all pytz --collect-all pytz
--collect-all six --collect-all six
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-4/-/jobs/artifacts/dev/download?job=win-dev-build
artifacts: artifacts:
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}" name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
paths: paths:
- app\ - app\
- .\crafty_commander.exe - .\crafty_commander.exe
exclude: exclude:
- app\classes\**\* - app\classes\**\*
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build
win-prod-build: win-prod-build:
stage: prod-deployment stage: prod-deployment
tags: tags:
- win64 - win64
cache: cache:
paths: paths:
- .venv/ - .venv/
rules: rules:
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
environment: environment:
name: production name: production
script: script:
- | - |
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
py -m venv .venv py -m venv .venv
.venv\Scripts\activate.ps1 .venv\Scripts\activate.ps1
pip install pyinstaller pip install pyinstaller
pip install -r requirements.txt pip install -r requirements.txt
- pyinstaller -F main.py - pyinstaller -F main.py
--distpath . --distpath .
--icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico --icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico
--name "crafty_commander" --name "crafty_commander"
--paths .venv\Lib\site-packages --paths .venv\Lib\site-packages
--hidden-import cryptography --hidden-import cryptography
--hidden-import cffi --hidden-import cffi
--hidden-import apscheduler --hidden-import apscheduler
--collect-all tzlocal --collect-all tzlocal
--collect-all tzdata --collect-all tzdata
--collect-all pytz --collect-all pytz
--collect-all six --collect-all six
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-4/-/jobs/artifacts/master/download?job=win-prod-build
artifacts: artifacts:
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}" name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
paths: paths:
- app\ - app\
- .\crafty_commander.exe - .\crafty_commander.exe
exclude: exclude:
- app\classes\**\* - app\classes\**\*
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build

View File

@ -78,7 +78,9 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have # --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes # no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W". # --disable=W".
disable=abstract-method, disable=C0330,
C0326,
abstract-method,
attribute-defined-outside-init, attribute-defined-outside-init,
bad-inline-option, bad-inline-option,
bare-except, bare-except,
@ -92,7 +94,6 @@ disable=abstract-method,
fixme, fixme,
import-error, import-error,
inconsistent-return-statements, inconsistent-return-statements,
invalid-name,
locally-disabled, locally-disabled,
logging-format-interpolation, logging-format-interpolation,
logging-fstring-interpolation, logging-fstring-interpolation,
@ -234,10 +235,20 @@ function-naming-style=snake_case
#function-rgx= #function-rgx=
# Good variable names which should always be accepted, separated by a comma. # Good variable names which should always be accepted, separated by a comma.
good-names=i, good-names=e,
ex,
f,
i,
id,
ip,
j, j,
k, k,
ex, p,
r,
rs,
s,
tz,
v,
Run, Run,
_ _
@ -306,7 +317,7 @@ indent-after-paren=4
indent-string=' ' indent-string=' '
# Maximum number of characters on a single line. # Maximum number of characters on a single line.
max-line-length=150 max-line-length=88
# Maximum number of lines in a module. # Maximum number of lines in a module.
max-module-lines=2000 max-module-lines=2000

View File

@ -2,15 +2,13 @@ FROM ubuntu:20.04
ENV DEBIAN_FRONTEND="noninteractive" ENV DEBIAN_FRONTEND="noninteractive"
LABEL maintainer="Dockerfile created by Zedifus <https://gitlab.com/zedifus>"
# Security Patch for CVE-2021-44228 # Security Patch for CVE-2021-44228
ENV LOG4J_FORMAT_MSG_NO_LOOKUPS=true ENV LOG4J_FORMAT_MSG_NO_LOOKUPS=true
# Create non-root user & required dirs # Create non-root user & required dirs
RUN useradd -g root -M crafty \ RUN useradd -g root -M crafty \
&& mkdir /commander \ && mkdir /crafty \
&& chown -R crafty:root /commander && chown -R crafty:root /crafty
# Install required system packages # Install required system packages
RUN apt-get update \ RUN apt-get update \
@ -32,7 +30,7 @@ RUN apt-get update \
# Switch to service user for installing crafty deps # Switch to service user for installing crafty deps
USER crafty USER crafty
WORKDIR /commander WORKDIR /crafty
COPY --chown=crafty:root requirements.txt ./ COPY --chown=crafty:root requirements.txt ./
RUN python3 -m venv ./.venv \ RUN python3 -m venv ./.venv \
&& . .venv/bin/activate \ && . .venv/bin/activate \
@ -53,6 +51,23 @@ EXPOSE 8443
EXPOSE 19132 EXPOSE 19132
EXPOSE 25500-25600 EXPOSE 25500-25600
# Start Crafty Commander through wrapper # Start Crafty through wrapper
ENTRYPOINT ["/commander/docker_launcher.sh"] ENTRYPOINT ["/crafty/docker_launcher.sh"]
CMD ["-v", "-d", "-i"] CMD ["-v", "-d", "-i"]
# Add meta labels
ARG BUILD_DATE
ARG BUILD_REF
ARG CRAFTY_VER
LABEL \
maintainer="Zedifus <https://gitlab.com/zedifus>" \
org.opencontainers.image.created=${BUILD_DATE} \
org.opencontainers.image.revision=${BUILD_REF} \
org.opencontainers.image.version=${CRAFTY_VER} \
org.opencontainers.image.title="Crafty Controller" \
org.opencontainers.image.description="A Game Server Control Panel / Launcher" \
org.opencontainers.image.url="https://craftycontrol.com/" \
org.opencontainers.image.documentation="https://wiki.craftycontrol.com/" \
org.opencontainers.image.source="https://gitlab.com/crafty-controller/crafty-4" \
org.opencontainers.image.vendor="Arcadia Technology, LLC." \
org.opencontainers.image.licenses="GPL-3.0"

View File

@ -1,4 +1,12 @@
# Crafty Controller 4.0.0-alpha.3.5 [![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.0--beta-orange)](https://gitlab.com/crafty-controller/crafty-4)
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4)
[![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master)
# Crafty Controller 4.0.0-beta
> Python based Control Panel for your Minecraft Server > Python based Control Panel for your Minecraft Server
## What is Crafty Controller? ## What is Crafty Controller?
@ -15,7 +23,7 @@ Project Homepage - https://craftycontrol.com
Discord Server - https://discord.gg/9VJPhCE Discord Server - https://discord.gg/9VJPhCE
Git Repository - https://gitlab.com/crafty-controller/crafty-web Git Repository - https://gitlab.com/crafty-controller/crafty-4
<br> <br>
@ -31,7 +39,7 @@ With `Crafty Controller 4.0` we have focused on building our DevOps Principles,
> __**⚠ 🔻WARNING: [WSL/WSL2 | WINDOWS 11 | DOCKER DESKTOP]🔻**__ <br> > __**⚠ 🔻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> 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> 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) 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-4/-/jobs/artifacts/master/download?job=win-prod-build), [Latest-Development](https://gitlab.com/crafty-controller/crafty-4/-/jobs/artifacts/dev/download?job=win-dev-build)
---- ----
@ -47,35 +55,16 @@ As the Dockerfile uses the permission structure of `crafty:root` **internally**
### - Using the registry image 🌎 ### - 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 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` The image is located at: `registry.gitlab.com/crafty-controller/crafty-4:latest`
| Branch | Status | | Branch | Status |
| ----------------- | ------------------------------------------------------------------ | | ----------------- | ------------------------------------------------------------------ |
| :latest | [![pipeline status](https://gitlab.com/crafty-controller/crafty-commander/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-commander/-/commits/master) | | :latest | [![pipeline status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master) |
| :dev | [![pipeline status](https://gitlab.com/crafty-controller/crafty-commander/badges/dev/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-commander/-/commits/dev) | | :dev | [![pipeline status](https://gitlab.com/crafty-controller/crafty-4/badges/dev/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/dev)
While the repository is still **private / pre-release**, <br>
Before you can pull the image you must authenticate docker with the Container Registry.
To authenticate you will need a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) **Here are some example methods for getting started🚀:**
with the minimum scope:
- For read (*pull*) access, `read_registry`.
- For write (*push*) access, `write_registry`.
When you have this just run:
```bash
$ docker login registry.gitlab.com -u <username> -p <token>
```
or
```bash
$ echo <token> | docker login registry.gitlab.com -u <username> --password-stdin
```
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 ```sh
# Make your compose file # Make your compose file
@ -86,8 +75,9 @@ version: '3'
services: services:
crafty: crafty:
container_name: crafty_commander container_name: crafty_container
image: registry.gitlab.com/crafty-controller/crafty-commander:latest image: registry.gitlab.com/crafty-controller/crafty-4:latest
restart: always
environment: environment:
- TZ=Etc/UTC - TZ=Etc/UTC
ports: ports:
@ -97,11 +87,11 @@ services:
- "19132:19132/udp" # BEDROCK - "19132:19132/udp" # BEDROCK
- "25500-25600:25500-25600" # MC SERV PORT RANGE - "25500-25600:25500-25600" # MC SERV PORT RANGE
volumes: volumes:
- ./docker/backups:/commander/backups - ./docker/backups:/crafty/backups
- ./docker/logs:/commander/logs - ./docker/logs:/crafty/logs
- ./docker/servers:/commander/servers - ./docker/servers:/crafty/servers
- ./docker/config:/commander/app/config - ./docker/config:/crafty/app/config
- ./docker/import:/commander/import - ./docker/import:/crafty/import
``` ```
```sh ```sh
$ docker-compose up -d && docker-compose logs -f $ docker-compose up -d && docker-compose logs -f
@ -111,19 +101,21 @@ $ docker-compose up -d && docker-compose logs -f
### **docker run:** ### **docker run:**
```sh ```sh
$ docker run \ $ docker run \
--name crafty_commander \ --name crafty_container \
--detach \
--restart always \
-p 8000:8000 \ -p 8000:8000 \
-p 8443:8443 \ -p 8443:8443 \
-p 8123:8123 \ -p 8123:8123 \
-p 19132:19132/udp \ -p 19132:19132/udp \
-p 25500-25600:25500-25600 \ -p 25500-25600:25500-25600 \
-e TZ=Etc/UTC \ -e TZ=Etc/UTC \
-v "/$(pwd)/docker/backups:/commander/backups" \ -v "/$(pwd)/docker/backups:/crafty/backups" \
-v "/$(pwd)/docker/logs:/commander/logs" \ -v "/$(pwd)/docker/logs:/crafty/logs" \
-v "/$(pwd)/docker/servers:/commander/servers" \ -v "/$(pwd)/docker/servers:/crafty/servers" \
-v "/$(pwd)/docker/config:/commander/app/config" \ -v "/$(pwd)/docker/config:/crafty/app/config" \
-v "/$(pwd)/docker/import:/commander/import" \ -v "/$(pwd)/docker/import:/crafty/import" \
registry.gitlab.com/crafty-controller/crafty-commander:latest registry.gitlab.com/crafty-controller/crafty-4:latest
``` ```
### **Building from the cloned repository:** ### **Building from the cloned repository:**
@ -136,18 +128,20 @@ If you'd rather not use `docker-compose` you can use the following `docker run`
$ docker build . -t crafty $ docker build . -t crafty
$ docker run \ $ docker run \
--name crafty_commander \ --name crafty_container \
--detach \
--restart always \
-p 8000:8000 \ -p 8000:8000 \
-p 8443:8443 \ -p 8443:8443 \
-p 8123:8123 \ -p 8123:8123 \
-p 19132:19132/udp \ -p 19132:19132/udp \
-p 25500-25600:25500-25600 \ -p 25500-25600:25500-25600 \
-e TZ=Etc/UTC \ -e TZ=Etc/UTC \
-v "/$(pwd)/docker/backups:/commander/backups" \ -v "/$(pwd)/docker/backups:/crafty/backups" \
-v "/$(pwd)/docker/logs:/commander/logs" \ -v "/$(pwd)/docker/logs:/crafty/logs" \
-v "/$(pwd)/docker/servers:/commander/servers" \ -v "/$(pwd)/docker/servers:/crafty/servers" \
-v "/$(pwd)/docker/config:/commander/app/config" \ -v "/$(pwd)/docker/config:/crafty/app/config" \
-v "/$(pwd)/docker/import:/commander/import" \ -v "/$(pwd)/docker/import:/crafty/import" \
crafty crafty
``` ```
A fresh build will take several minutes depending on your system, but will be rapid thereafter. A fresh build will take several minutes depending on your system, but will be rapid thereafter.

View File

@ -1,60 +1,85 @@
import logging import logging
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty from app.classes.models.crafty_permissions import (
PermissionsCrafty,
EnumPermissionsCrafty,
)
from app.classes.models.users import ApiKeys from app.classes.models.users import ApiKeys
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Crafty_Perms_Controller:
class CraftyPermsController:
@staticmethod @staticmethod
def list_defined_crafty_permissions(): def list_defined_crafty_permissions():
permissions_list = crafty_permissions.get_permissions_list() permissions_list = PermissionsCrafty.get_permissions_list()
return permissions_list return permissions_list
@staticmethod @staticmethod
def get_mask_crafty_permissions(user_id): def get_mask_crafty_permissions(user_id):
permissions_mask = crafty_permissions.get_crafty_permissions_mask(user_id) permissions_mask = PermissionsCrafty.get_crafty_permissions_mask(user_id)
return permissions_mask return permissions_mask
@staticmethod @staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Crafty, value): def set_permission(
return crafty_permissions.set_permission(permission_mask, permission_tested, value) permission_mask, permission_tested: EnumPermissionsCrafty, value
):
return PermissionsCrafty.set_permission(
permission_mask, permission_tested, value
)
@staticmethod @staticmethod
def can_create_server(user_id): def can_create_server(user_id):
return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Server_Creation) return PermissionsCrafty.can_add_in_crafty(
user_id, EnumPermissionsCrafty.SERVER_CREATION
)
@staticmethod @staticmethod
def can_add_user(): # Add back argument 'user_id' when you work on this def can_add_user(user_id):
#TODO: Complete if we need a User Addition limit return PermissionsCrafty.can_add_in_crafty(
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.User_Config) user_id, EnumPermissionsCrafty.USER_CONFIG
return True )
@staticmethod @staticmethod
def can_add_role(): # Add back argument 'user_id' when you work on this def can_add_role(user_id):
#TODO: Complete if we need a Role Addition limit return PermissionsCrafty.can_add_in_crafty(
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config) user_id, EnumPermissionsCrafty.ROLES_CONFIG
return True )
@staticmethod @staticmethod
def list_all_crafty_permissions_quantity_limits(): def list_all_crafty_permissions_quantity_limits():
return crafty_permissions.get_all_permission_quantity_list() return PermissionsCrafty.get_all_permission_quantity_list()
@staticmethod @staticmethod
def list_crafty_permissions_quantity_limits(user_id): def list_crafty_permissions_quantity_limits(user_id):
return crafty_permissions.get_permission_quantity_list(user_id) return PermissionsCrafty.get_permission_quantity_list(user_id)
@staticmethod @staticmethod
def get_crafty_permissions_list(user_id): def get_crafty_permissions_list(user_id):
permissions_mask = crafty_permissions.get_crafty_permissions_mask(user_id) permissions_mask = PermissionsCrafty.get_crafty_permissions_mask(user_id)
permissions_list = crafty_permissions.get_permissions(permissions_mask) permissions_list = PermissionsCrafty.get_permissions(permissions_mask)
return permissions_list return permissions_list
@staticmethod @staticmethod
def add_server_creation(user_id): def add_server_creation(user_id):
return crafty_permissions.add_server_creation(user_id) """Increase the "Server Creation" counter for this user
Args:
user_id (int): The modifiable user's ID
Returns:
int: The new count of servers created by this user
"""
return PermissionsCrafty.add_server_creation(user_id)
@staticmethod
def add_user_creation(user_id):
return PermissionsCrafty.add_user_creation(user_id)
@staticmethod
def add_role_creation(user_id):
return PermissionsCrafty.add_role_creation(user_id)
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys): def get_api_key_permissions_list(key: ApiKeys):
return crafty_permissions.get_api_key_permissions_list(key) return PermissionsCrafty.get_api_key_permissions_list(key)

View File

@ -1,120 +1,144 @@
import logging import logging
from app.classes.models.management import management_helper from app.classes.models.management import HelpersManagement
from app.classes.models.servers import servers_helper from app.classes.models.servers import HelperServers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Management_Controller:
#************************************************************************************************ class ManagementController:
def __init__(self, management_helper):
self.management_helper = management_helper
# **********************************************************************************
# Host_Stats Methods # Host_Stats Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_latest_hosts_stats(): def get_latest_hosts_stats():
return management_helper.get_latest_hosts_stats() return HelpersManagement.get_latest_hosts_stats()
#************************************************************************************************ # **********************************************************************************
# Commands Methods # Commands Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_unactioned_commands(): def get_unactioned_commands():
return management_helper.get_unactioned_commands() return HelpersManagement.get_unactioned_commands()
@staticmethod def send_command(self, user_id, server_id, remote_ip, command):
def send_command(user_id, server_id, remote_ip, command): server_name = HelperServers.get_server_friendly_name(server_id)
server_name = servers_helper.get_server_friendly_name(server_id)
# Example: Admin issued command start_server for server Survival # Example: Admin issued command start_server for server Survival
management_helper.add_to_audit_log(user_id, f"issued command {command} for server {server_name}", server_id, remote_ip) self.management_helper.add_to_audit_log(
management_helper.add_command(server_id, user_id, remote_ip, command) user_id,
f"issued command {command} for server {server_name}",
server_id,
remote_ip,
)
HelpersManagement.add_command(server_id, user_id, remote_ip, command)
@staticmethod @staticmethod
def mark_command_complete(command_id=None): def mark_command_complete(command_id=None):
return management_helper.mark_command_complete(command_id) return HelpersManagement.mark_command_complete(command_id)
#************************************************************************************************ # **********************************************************************************
# Audit_Log Methods # Audit_Log Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_actity_log(): def get_actity_log():
return management_helper.get_actity_log() return HelpersManagement.get_actity_log()
@staticmethod def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
def add_to_audit_log(user_id, log_msg, server_id=None, source_ip=None): return self.management_helper.add_to_audit_log(
return management_helper.add_to_audit_log(user_id, log_msg, server_id, source_ip) user_id, log_msg, server_id, source_ip
)
@staticmethod def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip):
def add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip): return self.management_helper.add_to_audit_log_raw(
return management_helper.add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip) user_name, user_id, server_id, log_msg, source_ip
)
#************************************************************************************************ # **********************************************************************************
# Schedules Methods # Schedules Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True): def create_scheduled_task(
return management_helper.create_scheduled_task( server_id,
server_id, action,
action, interval,
interval, interval_type,
interval_type, start_time,
start_time, command,
command, comment=None,
comment, enabled=True,
enabled ):
) return HelpersManagement.create_scheduled_task(
server_id,
action,
interval,
interval_type,
start_time,
command,
comment,
enabled,
)
@staticmethod @staticmethod
def delete_scheduled_task(schedule_id): def delete_scheduled_task(schedule_id):
return management_helper.delete_scheduled_task(schedule_id) return HelpersManagement.delete_scheduled_task(schedule_id)
@staticmethod @staticmethod
def update_scheduled_task(schedule_id, updates): def update_scheduled_task(schedule_id, updates):
return management_helper.update_scheduled_task(schedule_id, updates) return HelpersManagement.update_scheduled_task(schedule_id, updates)
@staticmethod @staticmethod
def get_scheduled_task(schedule_id): def get_scheduled_task(schedule_id):
return management_helper.get_scheduled_task(schedule_id) return HelpersManagement.get_scheduled_task(schedule_id)
@staticmethod @staticmethod
def get_scheduled_task_model(schedule_id): def get_scheduled_task_model(schedule_id):
return management_helper.get_scheduled_task_model(schedule_id) return HelpersManagement.get_scheduled_task_model(schedule_id)
@staticmethod @staticmethod
def get_child_schedules(sch_id): def get_child_schedules(sch_id):
return management_helper.get_child_schedules(sch_id) return HelpersManagement.get_child_schedules(sch_id)
@staticmethod @staticmethod
def get_schedules_by_server(server_id): def get_schedules_by_server(server_id):
return management_helper.get_schedules_by_server(server_id) return HelpersManagement.get_schedules_by_server(server_id)
@staticmethod @staticmethod
def get_schedules_all(): def get_schedules_all():
return management_helper.get_schedules_all() return HelpersManagement.get_schedules_all()
@staticmethod @staticmethod
def get_schedules_enabled(): def get_schedules_enabled():
return management_helper.get_schedules_enabled() return HelpersManagement.get_schedules_enabled()
#************************************************************************************************ # **********************************************************************************
# Backups Methods # Backups Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_backup_config(server_id): def get_backup_config(server_id):
return management_helper.get_backup_config(server_id) return HelpersManagement.get_backup_config(server_id)
@staticmethod def set_backup_config(
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None, compress: bool = False,): self,
return management_helper.set_backup_config(server_id, backup_path, max_backups, excluded_dirs, compress) server_id: int,
backup_path: str = None,
max_backups: int = None,
excluded_dirs: list = None,
compress: bool = False,
):
return self.management_helper.set_backup_config(
server_id, backup_path, max_backups, excluded_dirs, compress
)
@staticmethod @staticmethod
def get_excluded_backup_dirs(server_id: int): def get_excluded_backup_dirs(server_id: int):
return management_helper.get_excluded_backup_dirs(server_id) return HelpersManagement.get_excluded_backup_dirs(server_id)
@staticmethod def add_excluded_backup_dir(self, server_id: int, dir_to_add: str):
def add_excluded_backup_dir(server_id: int, dir_to_add: str): self.management_helper.add_excluded_backup_dir(server_id, dir_to_add)
management_helper.add_excluded_backup_dir(server_id, dir_to_add)
@staticmethod def del_excluded_backup_dir(self, server_id: int, dir_to_del: str):
def del_excluded_backup_dir(server_id: int, dir_to_del: str): self.management_helper.del_excluded_backup_dir(server_id, dir_to_del)
management_helper.del_excluded_backup_dir(server_id, dir_to_del)

View File

@ -1,32 +1,39 @@
import logging import logging
import typing as t
from app.classes.models.roles import roles_helper from app.classes.models.roles import HelperRoles
from app.classes.models.server_permissions import server_permissions from app.classes.models.server_permissions import PermissionsServers, RoleServers
from app.classes.models.users import users_helper from app.classes.shared.helpers import Helpers
from app.classes.shared.helpers import helper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Roles_Controller:
class RolesController:
def __init__(self, users_helper, roles_helper):
self.users_helper = users_helper
self.roles_helper = roles_helper
@staticmethod @staticmethod
def get_all_roles(): def get_all_roles():
return roles_helper.get_all_roles() return HelperRoles.get_all_roles()
@staticmethod
def get_all_role_ids():
return HelperRoles.get_all_role_ids()
@staticmethod @staticmethod
def get_roleid_by_name(role_name): def get_roleid_by_name(role_name):
return roles_helper.get_roleid_by_name(role_name) return HelperRoles.get_roleid_by_name(role_name)
@staticmethod @staticmethod
def get_role(role_id): def get_role(role_id):
return roles_helper.get_role(role_id) return HelperRoles.get_role(role_id)
@staticmethod @staticmethod
def update_role(role_id: str, role_data = None, permissions_mask: str = "00000000"): def update_role(role_id: str, role_data=None, permissions_mask: str = "00000000"):
if role_data is None: if role_data is None:
role_data = {} role_data = {}
base_data = Roles_Controller.get_role_with_servers(role_id) base_data = RolesController.get_role_with_servers(role_id)
up_data = {} up_data = {}
added_servers = set() added_servers = set()
removed_servers = set() removed_servers = set()
@ -34,49 +41,140 @@ class Roles_Controller:
if key == "role_id": if key == "role_id":
continue continue
elif key == "servers": elif key == "servers":
added_servers = role_data['servers'].difference(base_data['servers']) added_servers = set(role_data["servers"]).difference(
removed_servers = base_data['servers'].difference(role_data['servers']) set(base_data["servers"])
)
removed_servers = set(base_data["servers"]).difference(
set(role_data["servers"])
)
elif base_data[key] != role_data[key]: elif base_data[key] != role_data[key]:
up_data[key] = role_data[key] up_data[key] = role_data[key]
up_data['last_update'] = helper.get_time_as_string() up_data["last_update"] = Helpers.get_time_as_string()
logger.debug(f"role: {role_data} +server:{added_servers} -server{removed_servers}") logger.debug(
f"role: {role_data} +server:{added_servers} -server{removed_servers}"
)
for server in added_servers: for server in added_servers:
server_permissions.get_or_create(role_id, server, permissions_mask) PermissionsServers.get_or_create(role_id, server, permissions_mask)
for server in base_data['servers']: for server in base_data["servers"]:
server_permissions.update_role_permission(role_id, server, permissions_mask) PermissionsServers.update_role_permission(role_id, server, permissions_mask)
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point # TODO: This is horribly inefficient and we should be using bulk queries
server_permissions.delete_roles_permissions(role_id, removed_servers) # but im going for functionality at this point
PermissionsServers.delete_roles_permissions(role_id, removed_servers)
if up_data: if up_data:
roles_helper.update_role(role_id, up_data) HelperRoles.update_role(role_id, up_data)
@staticmethod @staticmethod
def add_role(role_name): def add_role(role_name):
return roles_helper.add_role(role_name) return HelperRoles.add_role(role_name)
class RoleServerJsonType(t.TypedDict):
server_id: t.Union[str, int]
permissions: str
@staticmethod @staticmethod
def remove_role(role_id): def get_server_ids_and_perms_from_role(
role_data = Roles_Controller.get_role_with_servers(role_id) role_id: t.Union[str, int]
server_permissions.delete_roles_permissions(role_id, role_data['servers']) ) -> t.List[RoleServerJsonType]:
users_helper.remove_roles_from_role_id(role_id) # FIXME: somehow retrieve only the server ids, not the whole servers
return roles_helper.remove_role(role_id) return [
{
"server_id": role_servers.server_id.server_id,
"permissions": role_servers.permissions,
}
for role_servers in (
RoleServers.select(
RoleServers.server_id, RoleServers.permissions
).where(RoleServers.role_id == role_id)
)
]
@staticmethod
def add_role_advanced(
name: str,
servers: t.Iterable[RoleServerJsonType],
) -> int:
"""Add a role with a name and a list of servers
Args:
name (str): The new role's name
servers (t.List[RoleServerJsonType]): The new role's servers
Returns:
int: The new role's ID
"""
role_id: t.Final[int] = HelperRoles.add_role(name)
for server in servers:
PermissionsServers.get_or_create(
role_id, server["server_id"], server["permissions"]
)
return role_id
@staticmethod
def update_role_advanced(
role_id: t.Union[str, int],
role_name: t.Optional[str],
servers: t.Optional[t.Iterable[RoleServerJsonType]],
) -> None:
"""Update a role with a name and a list of servers
Args:
role_id (t.Union[str, int]): The ID of the role to be modified
role_name (t.Optional[str]): An optional new name for the role
servers (t.Optional[t.Iterable[RoleServerJsonType]]): An optional list of servers for the role
""" # pylint: disable=line-too-long
logger.debug(f"updating role {role_id} with advanced options")
if servers is not None:
base_data = RolesController.get_role_with_servers(role_id)
server_ids = {server["server_id"] for server in servers}
server_permissions_map = {
server["server_id"]: server["permissions"] for server in servers
}
added_servers = server_ids.difference(set(base_data["servers"]))
removed_servers = set(base_data["servers"]).difference(server_ids)
same_servers = server_ids.intersection(set(base_data["servers"]))
logger.debug(
f"role: {role_id} +server:{added_servers} -server{removed_servers}"
)
for server_id in added_servers:
PermissionsServers.get_or_create(
role_id, server_id, server_permissions_map[server_id]
)
if len(removed_servers) != 0:
PermissionsServers.delete_roles_permissions(role_id, removed_servers)
for server_id in same_servers:
PermissionsServers.update_role_permission(
role_id, server_id, server_permissions_map[server_id]
)
if role_name is not None:
up_data = {
"role_name": role_name,
"last_update": Helpers.get_time_as_string(),
}
# TODO: do the last_update on the db side
HelperRoles.update_role(role_id, up_data)
def remove_role(self, role_id):
role_data = RolesController.get_role_with_servers(role_id)
PermissionsServers.delete_roles_permissions(role_id, role_data["servers"])
self.users_helper.remove_roles_from_role_id(role_id)
return self.roles_helper.remove_role(role_id)
@staticmethod @staticmethod
def role_id_exists(role_id): def role_id_exists(role_id):
return roles_helper.role_id_exists(role_id) return HelperRoles.role_id_exists(role_id)
@staticmethod @staticmethod
def get_role_with_servers(role_id): def get_role_with_servers(role_id):
role = roles_helper.get_role(role_id) role = HelperRoles.get_role(role_id)
if role: if role:
servers_query = server_permissions.get_servers_from_role(role_id) server_ids = PermissionsServers.get_server_ids_from_role(role_id)
# TODO: this query needs to be narrower role["servers"] = list(server_ids)
servers = set() # logger.debug("role: ({}) {}".format(role_id, role))
for s in servers_query:
servers.add(s.server_id.server_id)
role['servers'] = servers
#logger.debug("role: ({}) {}".format(role_id, role))
return role return role
else: else:
#logger.debug("role: ({}) {}".format(role_id, {})) # logger.debug("role: ({}) {}".format(role_id, {}))
return {} return {}

View File

@ -1,99 +1,117 @@
import logging import logging
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server from app.classes.models.server_permissions import (
from app.classes.models.users import users_helper, ApiKeys PermissionsServers,
from app.classes.models.roles import roles_helper EnumPermissionsServer,
from app.classes.models.servers import servers_helper )
from app.classes.shared.main_models import db_helper from app.classes.models.users import HelperUsers, ApiKeys
from app.classes.models.roles import HelperRoles
from app.classes.models.servers import HelperServers
from app.classes.models.server_stats import HelperServerStats
from app.classes.shared.main_models import DatabaseShortcuts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Server_Perms_Controller:
class ServerPermsController:
@staticmethod @staticmethod
def get_server_user_list(server_id): def get_server_user_list(server_id):
return server_permissions.get_server_user_list(server_id) return PermissionsServers.get_server_user_list(server_id)
@staticmethod @staticmethod
def list_defined_permissions(): def list_defined_permissions():
permissions_list = server_permissions.get_permissions_list() permissions_list = PermissionsServers.get_permissions_list()
return permissions_list return permissions_list
@staticmethod @staticmethod
def get_mask_permissions(role_id, server_id): def get_mask_permissions(role_id, server_id):
permissions_mask = server_permissions.get_permissions_mask(role_id, server_id) permissions_mask = PermissionsServers.get_permissions_mask(role_id, server_id)
return permissions_mask return permissions_mask
@staticmethod @staticmethod
def get_role_permissions(role_id): def get_role_permissions_dict(role_id):
permissions_list = server_permissions.get_role_permissions_list(role_id) return PermissionsServers.get_role_permissions_dict(role_id)
return permissions_list
@staticmethod @staticmethod
def add_role_server(server_id, role_id, rs_permissions="00000000"): def add_role_server(server_id, role_id, rs_permissions="00000000"):
return server_permissions.add_role_server(server_id, role_id, rs_permissions) return PermissionsServers.add_role_server(server_id, role_id, rs_permissions)
@staticmethod @staticmethod
def get_server_roles(server_id): def get_server_roles(server_id):
return server_permissions.get_server_roles(server_id) return PermissionsServers.get_server_roles(server_id)
@staticmethod @staticmethod
def backup_role_swap(old_server_id, new_server_id): def backup_role_swap(old_server_id, new_server_id):
role_list = server_permissions.get_server_roles(old_server_id) role_list = PermissionsServers.get_server_roles(old_server_id)
for role in role_list: for role in role_list:
server_permissions.add_role_server( PermissionsServers.add_role_server(
new_server_id, role.role_id, new_server_id,
server_permissions.get_permissions_mask(int(role.role_id), int(old_server_id))) role.role_id,
#server_permissions.add_role_server(new_server_id, role.role_id, '00001000') PermissionsServers.get_permissions_mask(
int(role.role_id), int(old_server_id)
),
)
# Permissions_Servers.add_role_server(
# new_server_id, role.role_id, "00001000"
# )
#************************************************************************************************ # **********************************************************************************
# Servers Permissions Methods # Servers Permissions Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_permissions_mask(role_id, server_id): def get_permissions_mask(role_id, server_id):
return server_permissions.get_permissions_mask(role_id, server_id) return PermissionsServers.get_permissions_mask(role_id, server_id)
@staticmethod @staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value): def set_permission(
return server_permissions.set_permission(permission_mask, permission_tested, value) permission_mask, permission_tested: EnumPermissionsServer, value
):
@staticmethod return PermissionsServers.set_permission(
def get_role_permissions_list(role_id): permission_mask, permission_tested, value
return server_permissions.get_role_permissions_list(role_id) )
@staticmethod @staticmethod
def get_user_id_permissions_list(user_id: str, server_id: str): def get_user_id_permissions_list(user_id: str, server_id: str):
return server_permissions.get_user_id_permissions_list(user_id, server_id) return PermissionsServers.get_user_id_permissions_list(user_id, server_id)
@staticmethod @staticmethod
def get_api_key_id_permissions_list(key_id: str, server_id: str): def get_api_key_id_permissions_list(key_id: str, server_id: str):
key = users_helper.get_user_api_key(key_id) key = HelperUsers.get_user_api_key(key_id)
return server_permissions.get_api_key_permissions_list(key, server_id) return PermissionsServers.get_api_key_permissions_list(key, server_id)
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str): def get_api_key_permissions_list(key: ApiKeys, server_id: str):
return server_permissions.get_api_key_permissions_list(key, server_id) return PermissionsServers.get_api_key_permissions_list(key, server_id)
@staticmethod @staticmethod
def get_authorized_servers_stats_from_roles(user_id): def get_authorized_servers_stats_from_roles(user_id):
user_roles = users_helper.get_user_roles_id(user_id) user_roles = HelperUsers.get_user_roles_id(user_id)
roles_list = [] roles_list = []
role_server = [] role_server = []
authorized_servers = [] authorized_servers = []
server_data = [] server_data = []
for u in user_roles: for user in user_roles:
roles_list.append(roles_helper.get_role(u.role_id)) roles_list.append(HelperRoles.get_role(user.role_id))
for r in roles_list: for role in roles_list:
role_test = server_permissions.get_role_servers_from_role_id(r.get('role_id')) role_test = PermissionsServers.get_role_servers_from_role_id(
for t in role_test: role.get("role_id")
role_server.append(t) )
for test in role_test:
role_server.append(test)
for s in role_server: for server in role_server:
authorized_servers.append(servers_helper.get_server_data_by_id(s.server_id)) authorized_servers.append(
HelperServers.get_server_data_by_id(server.server_id)
)
for s in authorized_servers: for server in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id')) latest = HelperServerStats.get_latest_server_stats(server.get("server_id"))
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0]}) server_data.append(
{
"server_data": server,
"stats": DatabaseShortcuts.return_rows(latest)[0],
}
)
return server_data return server_data

View File

@ -1,23 +1,31 @@
import os import os
import logging import logging
import json import json
import typing as t
from app.classes.controllers.roles_controller import Roles_Controller from app.classes.controllers.roles_controller import RolesController
from app.classes.models.servers import servers_helper from app.classes.models.servers import HelperServers
from app.classes.models.users import users_helper, ApiKeys from app.classes.models.server_stats import HelperServerStats
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server from app.classes.models.users import HelperUsers, ApiKeys
from app.classes.shared.helpers import helper from app.classes.models.server_permissions import (
from app.classes.shared.main_models import db_helper PermissionsServers,
EnumPermissionsServer,
)
from app.classes.shared.helpers import Helpers
from app.classes.shared.main_models import DatabaseShortcuts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Servers_Controller:
#************************************************************************************************ class ServersController:
def __init__(self, servers_helper):
self.servers_helper = servers_helper
# **********************************************************************************
# Generic Servers Methods # Generic Servers Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod
def create_server( def create_server(
self,
name: str, name: str,
server_uuid: str, server_uuid: str,
server_dir: str, server_dir: str,
@ -27,8 +35,31 @@ class Servers_Controller:
server_log_file: str, server_log_file: str,
server_stop: str, server_stop: str,
server_type: str, server_type: str,
server_port=25565): server_port: int = 25565,
return servers_helper.create_server( server_host: str = "127.0.0.1",
) -> int:
"""Create a server in the database
Args:
name: The name of the server
server_uuid: This is the UUID of the server
server_dir: The directory where the server is located
backup_path: The path to the backup folder
server_command: The command to start the server
server_file: The name of the server file
server_log_file: The path to the server log file
server_stop: This is the command to stop the server
server_type: This is the type of server you're creating.
server_port: The port the server will be monitored on, defaults to 25565
server_host: The host the server will be monitored on, defaults to 127.0.0.1
Returns:
int: The new server's id
Raises:
PeeweeException: If the server already exists
"""
return HelperServers.create_server(
name, name,
server_uuid, server_uuid,
server_dir, server_dir,
@ -38,199 +69,235 @@ class Servers_Controller:
server_log_file, server_log_file,
server_stop, server_stop,
server_type, server_type,
server_port) server_port,
server_host,
)
@staticmethod @staticmethod
def get_server_obj(server_id): def get_server_obj(server_id):
return servers_helper.get_server_obj(server_id) return HelperServers.get_server_obj(server_id)
@staticmethod @staticmethod
def update_server(server_obj): def update_server(server_obj):
return servers_helper.update_server(server_obj) return HelperServers.update_server(server_obj)
@staticmethod @staticmethod
def set_download(server_id): def set_download(server_id):
return servers_helper.set_download(server_id) return HelperServerStats.set_download(server_id)
@staticmethod @staticmethod
def finish_download(server_id): def finish_download(server_id):
return servers_helper.finish_download(server_id) return HelperServerStats.finish_download(server_id)
@staticmethod @staticmethod
def get_download_status(server_id): def get_download_status(server_id):
return servers_helper.get_download_status(server_id) return HelperServerStats.get_download_status(server_id)
@staticmethod def remove_server(self, server_id):
def remove_server(server_id): roles_list = PermissionsServers.get_roles_from_server(server_id)
roles_list = server_permissions.get_roles_from_server(server_id)
for role in roles_list: for role in roles_list:
role_id = role.role_id role_id = role.role_id
role_data = Roles_Controller.get_role_with_servers(role_id) role_data = RolesController.get_role_with_servers(role_id)
role_data['servers'] = {server_id} role_data["servers"] = {server_id}
server_permissions.delete_roles_permissions(role_id, role_data['servers']) PermissionsServers.delete_roles_permissions(role_id, role_data["servers"])
server_permissions.remove_roles_of_server(server_id) PermissionsServers.remove_roles_of_server(server_id)
servers_helper.remove_server(server_id) self.servers_helper.remove_server(server_id)
@staticmethod @staticmethod
def get_server_data_by_id(server_id): def get_server_data_by_id(server_id):
return servers_helper.get_server_data_by_id(server_id) return HelperServers.get_server_data_by_id(server_id)
#************************************************************************************************ # **********************************************************************************
# Servers Methods # Servers Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_all_defined_servers(): def get_all_defined_servers():
return servers_helper.get_all_defined_servers() return HelperServers.get_all_defined_servers()
@staticmethod @staticmethod
def get_authorized_servers(user_id): def get_authorized_servers(user_id):
server_data = [] server_data: t.List[t.Dict[str, t.Any]] = []
user_roles = users_helper.user_role_query(user_id) user_roles = HelperUsers.user_role_query(user_id)
for us in user_roles: for user in user_roles:
role_servers = server_permissions.get_role_servers_from_role_id(us.role_id) role_servers = PermissionsServers.get_role_servers_from_role_id(
user.role_id
)
for role in role_servers: for role in role_servers:
server_data.append(servers_helper.get_server_data_by_id(role.server_id)) server_data.append(HelperServers.get_server_data_by_id(role.server_id))
return server_data return server_data
@staticmethod
def get_authorized_users(server_id: str):
user_ids: t.Set[int] = set()
roles_list = PermissionsServers.get_roles_from_server(server_id)
for role in roles_list:
role_users = HelperUsers.get_users_from_role(role.role_id)
for user_role in role_users:
user_ids.add(user_role.user_id)
for user_id in HelperUsers.get_super_user_list():
user_ids.add(user_id)
return user_ids
@staticmethod @staticmethod
def get_all_servers_stats(): def get_all_servers_stats():
return servers_helper.get_all_servers_stats() return HelperServerStats.get_all_servers_stats()
@staticmethod @staticmethod
def get_authorized_servers_stats_api_key(api_key: ApiKeys): def get_authorized_servers_stats_api_key(api_key: ApiKeys):
server_data = [] server_data = []
authorized_servers = Servers_Controller.get_authorized_servers(api_key.user.user_id) authorized_servers = ServersController.get_authorized_servers(
api_key.user.user_id # TODO: API key authorized servers?
)
for s in authorized_servers: for server in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id')) latest = HelperServerStats.get_latest_server_stats(server.get("server_id"))
key_permissions = server_permissions.get_api_key_permissions_list(api_key, s.get('server_id')) key_permissions = PermissionsServers.get_api_key_permissions_list(
if Enum_Permissions_Server.Commands in key_permissions: api_key, server.get("server_id")
)
if EnumPermissionsServer.COMMANDS in key_permissions:
user_command_permission = True user_command_permission = True
else: else:
user_command_permission = False user_command_permission = False
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], server_data.append(
"user_command_permission": user_command_permission}) {
"server_data": server,
"stats": DatabaseShortcuts.return_rows(latest)[0],
"user_command_permission": user_command_permission,
}
)
return server_data return server_data
@staticmethod @staticmethod
def get_authorized_servers_stats(user_id): def get_authorized_servers_stats(user_id):
server_data = [] server_data = []
authorized_servers = Servers_Controller.get_authorized_servers(user_id) authorized_servers = ServersController.get_authorized_servers(user_id)
for s in authorized_servers: for server in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id')) latest = HelperServerStats.get_latest_server_stats(server.get("server_id"))
# TODO # TODO
user_permissions = server_permissions.get_user_id_permissions_list(user_id, s.get('server_id')) user_permissions = PermissionsServers.get_user_id_permissions_list(
if Enum_Permissions_Server.Commands in user_permissions: user_id, server.get("server_id")
)
if EnumPermissionsServer.COMMANDS in user_permissions:
user_command_permission = True user_command_permission = True
else: else:
user_command_permission = False user_command_permission = False
server_data.append({ server_data.append(
'server_data': s, {
'stats': db_helper.return_rows(latest)[0], "server_data": server,
'user_command_permission': user_command_permission "stats": DatabaseShortcuts.return_rows(latest)[0],
}) "user_command_permission": user_command_permission,
}
)
return server_data return server_data
@staticmethod @staticmethod
def get_server_friendly_name(server_id): def get_server_friendly_name(server_id):
return servers_helper.get_server_friendly_name(server_id) return HelperServers.get_server_friendly_name(server_id)
#************************************************************************************************ # **********************************************************************************
# Servers_Stats Methods # Servers_Stats Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_server_stats_by_id(server_id): def get_server_stats_by_id(server_id):
return servers_helper.get_server_stats_by_id(server_id) return HelperServerStats.get_server_stats_by_id(server_id)
@staticmethod @staticmethod
def server_id_exists(server_id): def server_id_exists(server_id):
return servers_helper.server_id_exists(server_id) return HelperServerStats.server_id_exists(server_id)
@staticmethod @staticmethod
def get_server_type_by_id(server_id): def get_server_type_by_id(server_id):
return servers_helper.get_server_type_by_id(server_id) return HelperServers.get_server_type_by_id(server_id)
@staticmethod @staticmethod
def server_id_authorized(server_id_a, user_id): def server_id_authorized(server_id_a, user_id):
user_roles = users_helper.user_role_query(user_id) user_roles = HelperUsers.user_role_query(user_id)
for role in user_roles: for role in user_roles:
for server_id_b in server_permissions.get_role_servers_from_role_id(role.role_id): for server_id_b in PermissionsServers.get_role_servers_from_role_id(
role.role_id
):
if str(server_id_a) == str(server_id_b.server_id): if str(server_id_a) == str(server_id_b.server_id):
return True return True
return False return False
@staticmethod @staticmethod
def is_crashed(server_id): def is_crashed(server_id):
return servers_helper.is_crashed(server_id) return HelperServerStats.is_crashed(server_id)
@staticmethod @staticmethod
def server_id_authorized_api_key(server_id: str, api_key: ApiKeys) -> bool: def server_id_authorized_api_key(server_id: str, api_key: ApiKeys) -> bool:
# TODO # TODO
return Servers_Controller.server_id_authorized(server_id, api_key.user.user_id) return ServersController.server_id_authorized(server_id, api_key.user.user_id)
# There is no view server permission # There is no view server permission
# permission_helper.both_have_perm(api_key) # permission_helper.both_have_perm(api_key)
@staticmethod @staticmethod
def set_update(server_id, value): def set_update(server_id, value):
return servers_helper.set_update(server_id, value) return HelperServerStats.set_update(server_id, value)
@staticmethod @staticmethod
def get_TTL_without_player(server_id): def get_ttl_without_player(server_id):
return servers_helper.get_TTL_without_player(server_id) return HelperServerStats.get_ttl_without_player(server_id)
@staticmethod @staticmethod
def can_stop_no_players(server_id, time_limit): def can_stop_no_players(server_id, time_limit):
return servers_helper.can_stop_no_players(server_id, time_limit) return HelperServerStats.can_stop_no_players(server_id, time_limit)
@staticmethod @staticmethod
def set_waiting_start(server_id, value): def set_waiting_start(server_id, value):
servers_helper.set_waiting_start(server_id, value) HelperServerStats.set_waiting_start(server_id, value)
@staticmethod @staticmethod
def get_waiting_start(server_id): def get_waiting_start(server_id):
return servers_helper.get_waiting_start(server_id) return HelperServerStats.get_waiting_start(server_id)
@staticmethod @staticmethod
def get_update_status(server_id): def get_update_status(server_id):
return servers_helper.get_update_status(server_id) return HelperServerStats.get_update_status(server_id)
#************************************************************************************************ # **********************************************************************************
# Servers Helpers Methods # Servers Helpers Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_banned_players(server_id): def get_banned_players(server_id):
stats = servers_helper.get_server_stats_by_id(server_id) stats = HelperServerStats.get_server_stats_by_id(server_id)
server_path = stats['server_id']['path'] server_path = stats["server_id"]["path"]
path = os.path.join(server_path, 'banned-players.json') path = os.path.join(server_path, "banned-players.json")
try: try:
with open(helper.get_os_understandable_path(path), encoding='utf-8') as file: with open(
Helpers.get_os_understandable_path(path), encoding="utf-8"
) as file:
content = file.read() content = file.read()
file.close() file.close()
except Exception as ex: except Exception as ex:
print (ex) print(ex)
return None return None
return json.loads(content) return json.loads(content)
def check_for_old_logs(self): def check_for_old_logs(self):
servers = servers_helper.get_all_defined_servers() servers = HelperServers.get_all_defined_servers()
for server in servers: for server in servers:
logs_path = os.path.split(server['log_path'])[0] logs_path = os.path.split(server["log_path"])[0]
latest_log_file = os.path.split(server['log_path'])[1] latest_log_file = os.path.split(server["log_path"])[1]
logs_delete_after = int(server['logs_delete_after']) logs_delete_after = int(server["logs_delete_after"])
if logs_delete_after == 0: if logs_delete_after == 0:
continue continue
log_files = list(filter( log_files = list(
lambda val: val != latest_log_file, filter(lambda val: val != latest_log_file, os.listdir(logs_path))
os.listdir(logs_path) )
))
for log_file in log_files: for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file) log_file_path = os.path.join(logs_path, log_file)
if helper.check_file_exists(log_file_path) and \ if Helpers.check_file_exists(
helper.is_file_older_than_x_days(log_file_path, logs_delete_after): log_file_path
) and Helpers.is_file_older_than_x_days(
log_file_path, logs_delete_after
):
os.remove(log_file_path) os.remove(log_file_path)

View File

@ -1,57 +1,134 @@
import logging import logging
from typing import Optional import typing as t
from app.classes.models.users import users_helper from app.classes.models.users import HelperUsers
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty from app.classes.models.crafty_permissions import (
from app.classes.shared.helpers import helper PermissionsCrafty,
from app.classes.shared.authentication import authentication EnumPermissionsCrafty,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Users_Controller:
#************************************************************************************************ class UsersController:
def __init__(self, helper, users_helper, authentication):
self.helper = helper
self.users_helper = users_helper
self.authentication = authentication
_permissions_props = {
"name": {
"type": "string",
"enum": [
permission.name
for permission in PermissionsCrafty.get_permissions_list()
],
},
"quantity": {"type": "number", "minimum": 0},
"enabled": {"type": "boolean"},
}
self.user_jsonschema_props: t.Final = {
"username": {
"type": "string",
"maxLength": 20,
"minLength": 4,
"pattern": "^[a-z0-9_]+$",
"examples": ["admin"],
"title": "Username",
},
"password": {
"type": "string",
"maxLength": 20,
"minLength": 4,
"examples": ["crafty"],
"title": "Password",
},
"email": {
"type": "string",
"format": "email",
"examples": ["default@example.com"],
"title": "E-Mail",
},
"enabled": {
"type": "boolean",
"examples": [True],
"title": "Enabled",
},
"lang": {
"type": "string",
"maxLength": 10,
"minLength": 2,
"examples": ["en"],
"title": "Language",
},
"superuser": {
"type": "boolean",
"examples": [False],
"title": "Superuser",
},
"permissions": {
"type": "array",
"items": {
"type": "object",
"properties": _permissions_props,
"required": ["name", "quantity", "enabled"],
},
},
"roles": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
},
},
"hints": {"type": "boolean"},
}
# **********************************************************************************
# Users Methods # Users Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_all_users(): def get_all_users():
return users_helper.get_all_users() return HelperUsers.get_all_users()
@staticmethod
def get_all_user_ids() -> t.List[int]:
return HelperUsers.get_all_user_ids()
@staticmethod @staticmethod
def get_id_by_name(username): def get_id_by_name(username):
return users_helper.get_user_id_by_name(username) return HelperUsers.get_user_id_by_name(username)
@staticmethod @staticmethod
def get_user_lang_by_id(user_id): def get_user_lang_by_id(user_id):
return users_helper.get_user_lang_by_id(user_id) return HelperUsers.get_user_lang_by_id(user_id)
@staticmethod @staticmethod
def get_user_by_id(user_id): def get_user_by_id(user_id):
return users_helper.get_user(user_id) return HelperUsers.get_user(user_id)
@staticmethod @staticmethod
def update_server_order(user_id, user_server_order): def update_server_order(user_id, user_server_order):
users_helper.update_server_order(user_id, user_server_order) HelperUsers.update_server_order(user_id, user_server_order)
@staticmethod @staticmethod
def get_server_order(user_id): def get_server_order(user_id):
return users_helper.get_server_order(user_id) return HelperUsers.get_server_order(user_id)
@staticmethod @staticmethod
def user_query(user_id): def user_query(user_id):
return users_helper.user_query(user_id) return HelperUsers.user_query(user_id)
@staticmethod @staticmethod
def set_support_path(user_id, support_path): def set_support_path(user_id, support_path):
users_helper.set_support_path(user_id, support_path) HelperUsers.set_support_path(user_id, support_path)
@staticmethod def update_user(self, user_id: str, user_data=None, user_crafty_data=None):
def update_user(user_id: str, user_data=None, user_crafty_data=None):
if user_crafty_data is None: if user_crafty_data is None:
user_crafty_data = {} user_crafty_data = {}
if user_data is None: if user_data is None:
user_data = {} user_data = {}
base_data = users_helper.get_user(user_id) base_data = HelperUsers.get_user(user_id)
up_data = {} up_data = {}
added_roles = set() added_roles = set()
removed_roles = set() removed_roles = set()
@ -59,110 +136,171 @@ class Users_Controller:
if key == "user_id": if key == "user_id":
continue continue
elif key == "roles": elif key == "roles":
added_roles = user_data['roles'].difference(base_data['roles']) added_roles = set(user_data["roles"]).difference(
removed_roles = base_data['roles'].difference(user_data['roles']) set(base_data["roles"])
)
removed_roles = set(base_data["roles"]).difference(
set(user_data["roles"])
)
elif key == "password": elif key == "password":
if user_data['password'] is not None and user_data['password'] != "": if user_data["password"] is not None and user_data["password"] != "":
up_data['password'] = helper.encode_pass(user_data['password']) up_data["password"] = self.helper.encode_pass(user_data["password"])
elif key == "lang":
up_data["lang"] = user_data["lang"]
elif key == "hints":
up_data["hints"] = user_data["hints"]
elif base_data[key] != user_data[key]: elif base_data[key] != user_data[key]:
up_data[key] = user_data[key] up_data[key] = user_data[key]
up_data['last_update'] = helper.get_time_as_string() up_data["last_update"] = self.helper.get_time_as_string()
up_data['lang'] = user_data['lang']
logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}") logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}")
for role in added_roles: for role in added_roles:
users_helper.get_or_create(user_id=user_id, role_id=role) HelperUsers.get_or_create(user_id=user_id, role_id=role)
permissions_mask = user_crafty_data.get('permissions_mask', '000') permissions_mask = user_crafty_data.get("permissions_mask", "000")
if 'server_quantity' in user_crafty_data: if "server_quantity" in user_crafty_data:
limit_server_creation = user_crafty_data['server_quantity'][ limit_server_creation = user_crafty_data["server_quantity"].get(
Enum_Permissions_Crafty.Server_Creation.name] EnumPermissionsCrafty.SERVER_CREATION.name, 0
)
limit_user_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.User_Config.name] limit_user_creation = user_crafty_data["server_quantity"].get(
limit_role_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Roles_Config.name] EnumPermissionsCrafty.USER_CONFIG.name, 0
else: )
limit_server_creation = 0 limit_role_creation = user_crafty_data["server_quantity"].get(
limit_user_creation = 0 EnumPermissionsCrafty.ROLES_CONFIG.name, 0
limit_role_creation = 0 )
else:
limit_server_creation = 0
limit_user_creation = 0
limit_role_creation = 0
crafty_permissions.add_or_update_user( PermissionsCrafty.add_or_update_user(
user_id, user_id,
permissions_mask, permissions_mask,
limit_server_creation, limit_server_creation,
limit_user_creation, limit_user_creation,
limit_role_creation) limit_role_creation,
)
users_helper.delete_user_roles(user_id, removed_roles) self.users_helper.delete_user_roles(user_id, removed_roles)
users_helper.update_user(user_id, up_data) self.users_helper.update_user(user_id, up_data)
def raw_update_user(self, user_id: int, up_data: t.Optional[t.Dict[str, t.Any]]):
"""Directly passes the data to the model helper.
Args:
user_id (int): The id of the user to update.
up_data (t.Optional[t.Dict[str, t.Any]]): Update data.
"""
self.users_helper.update_user(user_id, up_data)
def add_user(
self,
username,
password,
email="default@example.com",
enabled: bool = True,
superuser: bool = False,
):
return self.users_helper.add_user(
username,
password=password,
email=email,
enabled=enabled,
superuser=superuser,
)
@staticmethod @staticmethod
def add_user(username, password=None, email="default@example.com", enabled: bool = True, superuser: bool = False): def add_rawpass_user(
return users_helper.add_user(username, password=password, email=email, enabled=enabled, superuser=superuser) username,
password,
email="default@example.com",
enabled: bool = True,
superuser: bool = False,
):
return HelperUsers.add_rawpass_user(
username,
password=password,
email=email,
enabled=enabled,
superuser=superuser,
)
@staticmethod def remove_user(self, user_id):
def remove_user(user_id): return self.users_helper.remove_user(user_id)
return users_helper.remove_user(user_id)
@staticmethod @staticmethod
def user_id_exists(user_id): def user_id_exists(user_id):
return users_helper.user_id_exists(user_id) return HelperUsers.user_id_exists(user_id)
@staticmethod @staticmethod
def get_user_id_by_api_token(token: str) -> str: def set_prepare(user_id):
token_data = authentication.check_no_iat(token) return HelperUsers.set_prepare(user_id)
return token_data['user_id']
@staticmethod @staticmethod
def get_user_by_api_token(token: str): def stop_prepare(user_id):
_, user = authentication.check(token) return HelperUsers.stop_prepare(user_id)
def get_user_id_by_api_token(self, token: str) -> str:
token_data = self.authentication.check_no_iat(token)
return token_data["user_id"]
def get_user_by_api_token(self, token: str):
_, _, user = self.authentication.check_err(token)
return user return user
# ************************************************************************************************ def get_api_key_by_token(self, token: str):
key, _, _ = self.authentication.check(token)
return key
# **********************************************************************************
# User Roles Methods # User Roles Methods
# ************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_user_roles_id(user_id): def get_user_roles_id(user_id):
return users_helper.get_user_roles_id(user_id) return HelperUsers.get_user_roles_id(user_id)
@staticmethod @staticmethod
def get_user_roles_names(user_id): def get_user_roles_names(user_id):
return users_helper.get_user_roles_names(user_id) return HelperUsers.get_user_roles_names(user_id)
@staticmethod def add_role_to_user(self, user_id, role_id):
def add_role_to_user(user_id, role_id): return self.users_helper.add_role_to_user(user_id, role_id)
return users_helper.add_role_to_user(user_id, role_id)
@staticmethod def add_user_roles(self, user):
def add_user_roles(user): return self.users_helper.add_user_roles(user)
return users_helper.add_user_roles(user)
@staticmethod @staticmethod
def user_role_query(user_id): def user_role_query(user_id):
return users_helper.user_role_query(user_id) return HelperUsers.user_role_query(user_id)
# ************************************************************************************************ # **********************************************************************************
# Api Keys Methods # Api Keys Methods
# ************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_user_api_keys(user_id: str): def get_user_api_keys(user_id: str):
return users_helper.get_user_api_keys(user_id) return HelperUsers.get_user_api_keys(user_id)
@staticmethod @staticmethod
def get_user_api_key(key_id: str): def get_user_api_key(key_id: str):
return users_helper.get_user_api_key(key_id) return HelperUsers.get_user_api_key(key_id)
@staticmethod def add_user_api_key(
def add_user_api_key(name: str, user_id: str, superuser: bool = False, self,
server_permissions_mask: Optional[str] = None, name: str,
crafty_permissions_mask: Optional[str] = None): user_id: str,
return users_helper.add_user_api_key(name, user_id, superuser, server_permissions_mask, crafty_permissions_mask) superuser: bool = False,
server_permissions_mask: t.Optional[str] = None,
crafty_permissions_mask: t.Optional[str] = None,
):
return self.users_helper.add_user_api_key(
name, user_id, superuser, server_permissions_mask, crafty_permissions_mask
)
@staticmethod def delete_user_api_keys(self, user_id: str):
def delete_user_api_keys(user_id: str): return self.users_helper.delete_user_api_keys(user_id)
return users_helper.delete_user_api_keys(user_id)
@staticmethod def delete_user_api_key(self, key_id: str):
def delete_user_api_key(key_id: str): return self.users_helper.delete_user_api_key(key_id)
return users_helper.delete_user_api_key(key_id)

View File

@ -3,21 +3,22 @@ import socket
import time import time
import psutil import psutil
class BedrockPing: class BedrockPing:
magic = b'\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x12\x34\x56\x78' magic = b"\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x12\x34\x56\x78"
fields = { # (len, signed) fields = { # (len, signed)
"byte": (1, False), "byte": (1, False),
"long": (8, True), "long": (8, True),
"ulong": (8, False), "ulong": (8, False),
"magic": (16, False), "magic": (16, False),
"short": (2, True), "short": (2, True),
"ushort": (2, False), #unsigned short "ushort": (2, False), # unsigned short
"string": (2, False), #strlen is ushort "string": (2, False), # strlen is ushort
"bool": (1, False), "bool": (1, False),
"address": (7, False), "address": (7, False),
"uint24le": (3, False) "uint24le": (3, False),
} }
byte_order = 'big' byte_order = "big"
def __init__(self, bedrock_addr, bedrock_port, client_guid=0, timeout=5): def __init__(self, bedrock_addr, bedrock_port, client_guid=0, timeout=5):
self.addr = bedrock_addr self.addr = bedrock_addr
@ -36,51 +37,72 @@ class BedrockPing:
@staticmethod @staticmethod
def __slice(in_bytes, pattern): def __slice(in_bytes, pattern):
ret = [] ret = []
bi = 0 # bytes index bytes_index = 0
pi = 0 # pattern index pattern_index = 0
while bi < len(in_bytes): while bytes_index < len(in_bytes):
try: try:
f = BedrockPing.fields[pattern[pi]] field = BedrockPing.fields[pattern[pattern_index]]
except IndexError as index_error: except IndexError as index_error:
raise IndexError("Ran out of pattern with additional bytes remaining") from index_error raise IndexError(
if pattern[pi] == "string": "Ran out of pattern with additional bytes remaining"
shl = f[0] # string header length ) from index_error
sl = int.from_bytes(in_bytes[bi:bi+shl], BedrockPing.byte_order, signed=f[1]) # string length if pattern[pattern_index] == "string":
l = shl+sl string_header_length = field[0]
ret.append(in_bytes[bi+shl:bi+shl+sl].decode('ascii')) string_length = int.from_bytes(
elif pattern[pi] == "magic": in_bytes[bytes_index : bytes_index + string_header_length],
l = f[0] # length of field BedrockPing.byte_order,
ret.append(in_bytes[bi:bi+l]) signed=field[1],
)
length = string_header_length + string_length
ret.append(
in_bytes[
bytes_index
+ string_header_length : bytes_index
+ string_header_length
+ string_length
].decode("ascii")
)
elif pattern[pattern_index] == "magic":
length = field[0]
ret.append(in_bytes[bytes_index : bytes_index + length])
else: else:
l = f[0] # length of field length = field[0]
ret.append(int.from_bytes(in_bytes[bi:bi+l], BedrockPing.byte_order, signed=f[1])) ret.append(
bi+=l int.from_bytes(
pi+=1 in_bytes[bytes_index : bytes_index + length],
BedrockPing.byte_order,
signed=field[1],
)
)
bytes_index += length
pattern_index += 1
return ret return ret
@staticmethod @staticmethod
def __get_time(): def __get_time():
#return time.time_ns() // 1000000 # return time.time_ns() // 1000000
return time.perf_counter_ns() // 1000000 return time.perf_counter_ns() // 1000000
def __sendping(self): def __sendping(self):
pack_id = BedrockPing.__byter(0x01, 'byte') pack_id = BedrockPing.__byter(0x01, "byte")
now = BedrockPing.__byter(BedrockPing.__get_time(), 'ulong') now = BedrockPing.__byter(BedrockPing.__get_time(), "ulong")
guid = self.guid_bytes guid = self.guid_bytes
d2s = pack_id+now+BedrockPing.magic+guid d2s = pack_id + now + BedrockPing.magic + guid
#print("S:", d2s) # print("S:", d2s)
self.sock.sendto(d2s, (self.addr, self.port)) self.sock.sendto(d2s, (self.addr, self.port))
def __recvpong(self): def __recvpong(self):
data = self.sock.recv(4096) data = self.sock.recv(4096)
if data[0] == 0x1c: if data[0] == 0x1C:
ret = {} ret = {}
sliced = BedrockPing.__slice(data,["byte","ulong","ulong","magic","string"]) sliced = BedrockPing.__slice(
data, ["byte", "ulong", "ulong", "magic", "string"]
)
if sliced[3] != BedrockPing.magic: if sliced[3] != BedrockPing.magic:
raise ValueError(f"Incorrect magic received ({sliced[3]})") raise ValueError(f"Incorrect magic received ({sliced[3]})")
ret["server_guid"] = sliced[2] ret["server_guid"] = sliced[2]
ret["server_string_raw"] = sliced[4] ret["server_string_raw"] = sliced[4]
server_info = sliced[4].split(';') server_info = sliced[4].split(";")
ret["server_edition"] = server_info[0] ret["server_edition"] = server_info[0]
ret["server_motd"] = (server_info[1], server_info[7]) ret["server_motd"] = (server_info[1], server_info[7])
ret["server_protocol_version"] = server_info[2] ret["server_protocol_version"] = server_info[2]
@ -103,5 +125,7 @@ class BedrockPing:
self.__sendping() self.__sendping()
return self.__recvpong() return self.__recvpong()
except ValueError as e: except ValueError as e:
print(f"E: {e}, checking next packet. Retries remaining: {rtr}/{retries}") print(
f"E: {e}, checking next packet. Retries remaining: {rtr}/{retries}"
)
rtr -= 1 rtr -= 1

View File

@ -9,28 +9,29 @@ import uuid
import random import random
from app.classes.minecraft.bedrock_ping import BedrockPing from app.classes.minecraft.bedrock_ping import BedrockPing
from app.classes.shared.console import console from app.classes.shared.console import Console
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Server: class Server:
def __init__(self, data): def __init__(self, data):
self.description = data.get('description') self.description = data.get("description")
# print(self.description) # print(self.description)
if isinstance(self.description, dict): if isinstance(self.description, dict):
# cat server # cat server
if "translate" in self.description: if "translate" in self.description:
self.description = self.description['translate'] self.description = self.description["translate"]
# waterfall / bungee # waterfall / bungee
elif 'extra' in self.description: elif "extra" in self.description:
lines = [] lines = []
description = self.description description = self.description
if 'extra' in description.keys(): if "extra" in description.keys():
for e in description['extra']: for e in description["extra"]:
#Conversion format code needed only for Java Version # Conversion format code needed only for Java Version
lines.append(get_code_format("reset")) lines.append(get_code_format("reset"))
if "bold" in e.keys(): if "bold" in e.keys():
lines.append(get_code_format("bold")) lines.append(get_code_format("bold"))
@ -43,77 +44,76 @@ class Server:
if "obfuscated" in e.keys(): if "obfuscated" in e.keys():
lines.append(get_code_format("obfuscated")) lines.append(get_code_format("obfuscated"))
if "color" in e.keys(): if "color" in e.keys():
lines.append(get_code_format(e['color'])) lines.append(get_code_format(e["color"]))
#Then append the text # Then append the text
if "text" in e.keys(): if "text" in e.keys():
if e['text'] == '\n': if e["text"] == "\n":
lines.append("§§") lines.append("§§")
else: else:
lines.append(e['text']) lines.append(e["text"])
total_text = " ".join(lines) total_text = " ".join(lines)
self.description = total_text self.description = total_text
# normal MC # normal MC
else: else:
self.description = self.description['text'] self.description = self.description["text"]
self.icon = base64.b64decode(data.get('favicon', '')[22:]) self.icon = base64.b64decode(data.get("favicon", "")[22:])
try: try:
self.players = Players(data['players']).report() self.players = Players(data["players"]).report()
except KeyError: except KeyError:
logger.error("Error geting player information key error") logger.error("Error geting player information key error")
self.players = [] self.players = []
self.version = data['version']['name'] self.version = data["version"]["name"]
self.protocol = data['version']['protocol'] self.protocol = data["version"]["protocol"]
class Players(list): class Players(list):
def __init__(self, data): def __init__(self, data):
super().__init__(Player(x) for x in data.get('sample', [])) super().__init__(Player(x) for x in data.get("sample", []))
self.max = data['max'] self.max = data["max"]
self.online = data['online'] self.online = data["online"]
def report(self): def report(self):
players = [] players = []
for x in self: for player in self:
players.append(str(x)) players.append(str(player))
r_data = { r_data = {"online": self.online, "max": self.max, "players": players}
'online': self.online,
'max': self.max,
'players': players
}
return json.dumps(r_data) return json.dumps(r_data)
class Player: class Player:
def __init__(self, data): def __init__(self, data):
self.id = data['id'] self.id = data["id"]
self.name = data['name'] self.name = data["name"]
def __str__(self): def __str__(self):
return self.name return self.name
def get_code_format(format_name): def get_code_format(format_name):
root_dir = os.path.abspath(os.path.curdir) root_dir = os.path.abspath(os.path.curdir)
format_file = os.path.join(root_dir, 'app', 'config', 'motd_format.json') format_file = os.path.join(root_dir, "app", "config", "motd_format.json")
try: try:
with open(format_file, "r", encoding='utf-8') as f: with open(format_file, "r", encoding="utf-8") as f:
data = json.load(f) data = json.load(f)
if format_name in data.keys(): if format_name in data.keys():
return data.get(format_name) return data.get(format_name)
else: else:
logger.error(f"Format MOTD Error: format name {format_name} does not exist") 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") Console.error(
f"Format MOTD Error: format name {format_name} does not exist"
)
return "" return ""
except Exception as e: except Exception as e:
logger.critical(f"Config File Error: Unable to read {format_file} due to {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}") Console.critical(f"Config File Error: Unable to read {format_file} due to {e}")
return "" return ""
@ -128,10 +128,10 @@ def ping(ip, port):
if not k: if not k:
return 0 return 0
k = k[0] k = k[0]
i |= (k & 0x7f) << (j * 7) i |= (k & 0x7F) << (j * 7)
j += 1 j += 1
if j > 5: if j > 5:
raise ValueError('var_int too big') raise ValueError("var_int too big")
if not k & 0x80: if not k & 0x80:
return i return i
@ -143,15 +143,15 @@ def ping(ip, port):
return False return False
try: try:
host = ip.encode('utf-8') host = ip.encode("utf-8")
data = b'' # wiki.vg/Server_List_Ping data = b"" # wiki.vg/Server_List_Ping
data += b'\x00' # packet ID data += b"\x00" # packet ID
data += b'\x04' # protocol variant data += b"\x04" # protocol variant
data += struct.pack('>b', len(host)) + host data += struct.pack(">b", len(host)) + host
data += struct.pack('>H', port) data += struct.pack(">H", port)
data += b'\x01' # next state data += b"\x01" # next state
data = struct.pack('>b', len(data)) + data data = struct.pack(">b", len(data)) + data
sock.sendall(data + b'\x01\x00') # handshake + status ping sock.sendall(data + b"\x01\x00") # handshake + status ping
length = read_var_int() # full packet length length = read_var_int() # full packet length
if length < 10: if length < 10:
if length < 0: if length < 0:
@ -161,7 +161,7 @@ def ping(ip, port):
sock.recv(1) # packet type, 0 for pings sock.recv(1) # packet type, 0 for pings
length = read_var_int() # string length length = read_var_int() # string length
data = b'' data = b""
while len(data) != length: while len(data) != length:
chunk = sock.recv(length - len(data)) chunk = sock.recv(length - len(data))
if not chunk: if not chunk:
@ -176,13 +176,14 @@ def ping(ip, port):
finally: finally:
sock.close() sock.close()
# For the rest of requests see wiki.vg/Protocol # For the rest of requests see wiki.vg/Protocol
def ping_bedrock(ip, port): def ping_bedrock(ip, port):
rd = random.Random() rand = random.Random()
try: try:
#pylint: disable=consider-using-f-string # pylint: disable=consider-using-f-string
rd.seed(''.join(re.findall('..', '%012x' % uuid.getnode()))) rand.seed("".join(re.findall("..", "%012x" % uuid.getnode())))
client_guid = uuid.UUID(int=rd.getrandbits(32)).int client_guid = uuid.UUID(int=rand.getrandbits(32)).int
except: except:
client_guid = 0 client_guid = 0
try: try:

View File

@ -1,44 +1,45 @@
import pprint import pprint
import os import os
class ServerProps:
class ServerProps:
def __init__(self, filepath): def __init__(self, filepath):
self.filepath = filepath self.filepath = filepath
self.props = self._parse() self.props = self._parse()
def _parse(self): def _parse(self):
"""Loads and parses the file specified in self.filepath""" # Loads and parses the file specified in self.filepath
with open(self.filepath, encoding='utf-8') as fp: with open(self.filepath, encoding="utf-8") as full_path:
line = fp.readline() line = full_path.readline()
d = {} dictionary = {}
if os.path.exists(".header"): if os.path.exists(".header"):
os.remove(".header") os.remove(".header")
while line: while line:
if '#' != line[0]: if "#" != line[0]:
s = line string = line
s1 = s[:s.find('=')] string1 = string[: string.find("=")]
if '\n' in s: if "\n" in string:
s2 = s[s.find('=')+1:s.find('\n')] string2 = string[string.find("=") + 1 : string.find("\n")]
else: else:
s2 = s[s.find('=')+1:] string2 = string[string.find("=") + 1 :]
d[s1] = s2 dictionary[string1] = string2
else: else:
with open(".header", "a+", encoding='utf-8') as h: with open(".header", "a+", encoding="utf-8") as header:
h.write(line) header.write(line)
line = fp.readline() line = full_path.readline()
return d return dictionary
def print(self): def print(self):
"""Prints the properties dictionary (using pprint)""" # Prints the properties dictionary (using pprint)
pprint.pprint(self.props) pprint.pprint(self.props)
def get(self): def get(self):
"""Returns the properties dictionary""" # Returns the properties dictionary
return self.props return self.props
def update(self, key, val): def update(self, key, val):
"""Updates property in the properties dictionary [ update("pvp", "true") ] and returns boolean condition""" # Updates property in the properties dictionary [ update("pvp", "true") ]
# and returns boolean condition
if key in self.props.keys(): if key in self.props.keys():
self.props[key] = val self.props[key] = val
return True return True
@ -46,10 +47,10 @@ class ServerProps:
return False return False
def save(self): def save(self):
"""Writes to the new file""" # Writes to the new file
with open(self.filepath, "a+", encoding='utf-8') as f: with open(self.filepath, "a+", encoding="utf-8") as f:
f.truncate(0) f.truncate(0)
with open(".header", encoding='utf-8') as header: with open(".header", encoding="utf-8") as header:
line = header.readline() line = header.readline()
while line: while line:
f.write(line) f.write(line)

View File

@ -4,45 +4,32 @@ import time
import shutil import shutil
import logging import logging
from datetime import datetime from datetime import datetime
import requests
from app.classes.controllers.servers_controller import Servers_Controller from app.classes.controllers.servers_controller import ServersController
from app.classes.models.server_permissions import server_permissions from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try:
import requests
except ModuleNotFoundError as err:
helper.auto_installer_fix(err)
class ServerJars: class ServerJars:
def __init__(self, helper):
def __init__(self): self.helper = helper
self.base_url = "https://serverjars.com" self.base_url = "https://serverjars.com"
def _get_api_result(self, call_url: str): def _get_api_result(self, call_url: str):
full_url = f"{self.base_url}{call_url}" full_url = f"{self.base_url}{call_url}"
try: try:
r = requests.get(full_url, timeout=2) response = requests.get(full_url, timeout=2)
response.raise_for_status()
if r.status_code not in [200, 201]: api_data = json.loads(response.content)
return {}
except Exception as e: except Exception as e:
logger.error(f"Unable to connect to serverjar.com api due to error: {e}") logger.error(f"Unable to load {full_url} api due to error: {e}")
return {} return {}
try: api_result = api_data.get("status")
api_data = json.loads(r.content) api_response = api_data.get("response", {})
except Exception as 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": if api_result != "success":
logger.error(f"Api returned a failed status: {api_result}") logger.error(f"Api returned a failed status: {api_result}")
@ -50,12 +37,11 @@ class ServerJars:
return api_response return api_response
@staticmethod def _read_cache(self):
def _read_cache(): cache_file = self.helper.serverjar_cache
cache_file = helper.serverjar_cache
cache = {} cache = {}
try: try:
with open(cache_file, "r", encoding='utf-8') as f: with open(cache_file, "r", encoding="utf-8") as f:
cache = json.load(f) cache = json.load(f)
except Exception as e: except Exception as e:
@ -65,39 +51,16 @@ class ServerJars:
def get_serverjar_data(self): def get_serverjar_data(self):
data = self._read_cache() data = self._read_cache()
return data.get('servers') return data.get("servers")
def get_serverjar_data_sorted(self):
data = self.get_serverjar_data()
def str_to_int(x, counter=0):
try:
return ord(x[0]) + str_to_int(x[1:], counter + 1) + len(x)
except IndexError:
return 0
def to_int(x):
try:
return int(x)
except ValueError:
temp = x.split('-')
return to_int(temp[0]) + str_to_int(temp[1]) / 100000
sort_key_fn = lambda x: [to_int(y) for y in x.split('.')]
for key in data.keys():
data[key] = sorted(data[key], key=sort_key_fn)
return data
def _check_api_alive(self): def _check_api_alive(self):
logger.info("Checking serverjars.com API status") logger.info("Checking serverjars.com API status")
check_url = f"{self.base_url}/api/fetchTypes" check_url = f"{self.base_url}/api/fetchTypes"
try: try:
r = requests.get(check_url, timeout=2) response = requests.get(check_url, timeout=2)
if r.status_code in [200, 201]: if response.status_code in [200, 201]:
logger.info("Serverjars.com API is alive") logger.info("Serverjars.com API is alive")
return True return True
except Exception as e: except Exception as e:
@ -109,8 +72,8 @@ class ServerJars:
def refresh_cache(self): def refresh_cache(self):
cache_file = helper.serverjar_cache cache_file = self.helper.serverjar_cache
cache_old = helper.is_file_older_than_x_days(cache_file) cache_old = self.helper.is_file_older_than_x_days(cache_file)
# debug override # debug override
# cache_old = True # cache_old = True
@ -125,10 +88,7 @@ class ServerJars:
if cache_old: if cache_old:
logger.info("Cache file is over 1 day old, refreshing") logger.info("Cache file is over 1 day old, refreshing")
now = datetime.now() now = datetime.now()
data = { data = {"last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"), "servers": {}}
'last_refreshed': now.strftime("%m/%d/%Y, %H:%M:%S"),
'servers': {}
}
jar_types = self._get_server_type_list() jar_types = self._get_server_type_list()
@ -140,81 +100,89 @@ class ServerJars:
# jar versions for this server # jar versions for this server
versions = self._get_jar_details(s) versions = self._get_jar_details(s)
# add these versions (a list) to the dict with a key of the server type # add these versions (a list) to the dict with
data['servers'].update({ # a key of the server type
s: versions data["servers"].update({s: versions})
})
# save our cache # save our cache
try: try:
with open(cache_file, "w", encoding='utf-8') as f: with open(cache_file, "w", encoding="utf-8") as f:
f.write(json.dumps(data, indent=4)) f.write(json.dumps(data, indent=4))
logger.info("Cache file refreshed") logger.info("Cache file refreshed")
except Exception as e: except Exception as e:
logger.error(f"Unable to update serverjars.com cache file: {e}") logger.error(f"Unable to update serverjars.com cache file: {e}")
def _get_jar_details(self, jar_type='servers'): def _get_jar_details(self, jar_type="servers"):
url = f'/api/fetchAll/{jar_type}' url = f"/api/fetchAll/{jar_type}"
response = self._get_api_result(url) response = self._get_api_result(url)
temp = [] temp = []
for v in response: for v in response:
temp.append(v.get('version')) temp.append(v.get("version"))
time.sleep(.5) time.sleep(0.5)
return temp return temp
def _get_server_type_list(self): def _get_server_type_list(self):
url = '/api/fetchTypes/' url = "/api/fetchTypes/"
response = self._get_api_result(url) response = self._get_api_result(url)
return response return response
def download_jar(self, server, version, path, server_id): 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 = threading.Thread(
name=f"server_download-{server_id}-{server}-{version}",
target=self.a_download_jar,
daemon=True,
args=(server, version, path, server_id),
)
update_thread.start() update_thread.start()
def a_download_jar(self, server, version, path, server_id): def a_download_jar(self, server, version, path, server_id):
#delaying download for server register to finish # delaying download for server register to finish
time.sleep(3) time.sleep(3)
fetch_url = f"{self.base_url}/api/fetchJar/{server}/{version}" fetch_url = f"{self.base_url}/api/fetchJar/{server}/{version}"
server_users = server_permissions.get_server_user_list(server_id) server_users = PermissionsServers.get_server_user_list(server_id)
# We need to make sure the server is registered before
#We need to make sure the server is registered before we submit a db update for it's stats. # we submit a db update for it's stats.
while True: while True:
try: try:
Servers_Controller.set_download(server_id) ServersController.set_download(server_id)
for user in server_users: for user in server_users:
websocket_helper.broadcast_user(user, 'send_start_reload', { self.helper.websocket_helper.broadcast_user(
}) user, "send_start_reload", {}
)
break break
except: except Exception as ex:
logger.debug("server not registered yet. Delaying download.") logger.debug(f"server not registered yet. Delaying download - {ex}")
# open a file stream # open a file stream
with requests.get(fetch_url, timeout=2, stream=True) as r: with requests.get(fetch_url, timeout=2, stream=True) as r:
try: try:
with open(path, 'wb') as output: with open(path, "wb") as output:
shutil.copyfileobj(r.raw, output) shutil.copyfileobj(r.raw, output)
Servers_Controller.finish_download(server_id) ServersController.finish_download(server_id)
for user in server_users: for user in server_users:
websocket_helper.broadcast_user(user, 'notification', "Executable download finished") self.helper.websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3) time.sleep(3)
websocket_helper.broadcast_user(user, 'send_start_reload', { self.helper.websocket_helper.broadcast_user(
}) user, "send_start_reload", {}
)
return True return True
except Exception as e: except Exception as e:
logger.error(f"Unable to save jar to {path} due to error:{e}") logger.error(f"Unable to save jar to {path} due to error:{e}")
Servers_Controller.finish_download(server_id) ServersController.finish_download(server_id)
server_users = server_permissions.get_server_user_list(server_id) server_users = PermissionsServers.get_server_user_list(server_id)
for user in server_users: for user in server_users:
websocket_helper.broadcast_user(user, 'notification', "Executable download finished") self.helper.websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3) time.sleep(3)
websocket_helper.broadcast_user(user, 'send_start_reload', { self.helper.websocket_helper.broadcast_user(
}) user, "send_start_reload", {}
)
return False return False
server_jar_obj = ServerJars()

View File

@ -5,15 +5,16 @@ import base64
import psutil import psutil
from app.classes.minecraft.mc_ping import ping from app.classes.minecraft.mc_ping import ping
from app.classes.models.management import Host_Stats from app.classes.models.management import HostStats
from app.classes.models.servers import servers_helper from app.classes.models.servers import HelperServers
from app.classes.shared.helpers import helper from app.classes.shared.helpers import Helpers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Stats:
def __init__(self, controller): class Stats:
def __init__(self, helper, controller):
self.helper = helper
self.controller = controller self.controller = controller
def get_node_stats(self): def get_node_stats(self):
@ -24,30 +25,26 @@ class Stats:
except NotImplementedError: except NotImplementedError:
cpu_freq = psutil._common.scpufreq(current=0, min=0, max=0) cpu_freq = psutil._common.scpufreq(current=0, min=0, max=0)
node_stats = { node_stats = {
'boot_time': str(boot_time), "boot_time": str(boot_time),
'cpu_usage': psutil.cpu_percent(interval=0.5) / psutil.cpu_count(), "cpu_usage": psutil.cpu_percent(interval=0.5) / psutil.cpu_count(),
'cpu_count': psutil.cpu_count(), "cpu_count": psutil.cpu_count(),
'cpu_cur_freq': round(cpu_freq[0], 2), "cpu_cur_freq": round(cpu_freq[0], 2),
'cpu_max_freq': cpu_freq[2], "cpu_max_freq": cpu_freq[2],
'mem_percent': psutil.virtual_memory()[2], "mem_percent": psutil.virtual_memory()[2],
'mem_usage': helper.human_readable_file_size(psutil.virtual_memory()[3]), "mem_usage": Helpers.human_readable_file_size(psutil.virtual_memory()[3]),
'mem_total': helper.human_readable_file_size(psutil.virtual_memory()[0]), "mem_total": Helpers.human_readable_file_size(psutil.virtual_memory()[0]),
'disk_data': self._all_disk_usage() "disk_data": self._all_disk_usage(),
} }
#server_stats = self.get_servers_stats() # server_stats = self.get_servers_stats()
#data['servers'] = server_stats # data['servers'] = server_stats
data['node_stats'] = node_stats data["node_stats"] = node_stats
return data return data
@staticmethod @staticmethod
def _get_process_stats(process): def _get_process_stats(process):
if process is None: if process is None:
process_stats = { process_stats = {"cpu_usage": 0, "memory_usage": 0, "mem_percentage": 0}
'cpu_usage': 0,
'memory_usage': 0,
'mem_percentage': 0
}
return process_stats return process_stats
else: else:
process_pid = process.pid process_pid = process.pid
@ -63,31 +60,35 @@ class Stats:
# this is a faster way of getting data for a process # this is a faster way of getting data for a process
with p.oneshot(): with p.oneshot():
process_stats = { process_stats = {
'cpu_usage': real_cpu, "cpu_usage": real_cpu,
'memory_usage': helper.human_readable_file_size(p.memory_info()[0]), "memory_usage": Helpers.human_readable_file_size(
'mem_percentage': round(p.memory_percent(), 0) p.memory_info()[0]
),
"mem_percentage": round(p.memory_percent(), 0),
} }
return process_stats return process_stats
except Exception as e: except Exception as e:
logger.error(f"Unable to get process details for pid: {process_pid} due to error: {e}") logger.error(
f"Unable to get process details for pid: {process_pid} Error: {e}"
)
# Dummy Data # Dummy Data
process_stats = { process_stats = {
'cpu_usage': 0, "cpu_usage": 0,
'memory_usage': 0, "memory_usage": 0,
} }
return process_stats return process_stats
# shamelessly stolen from https://github.com/giampaolo/psutil/blob/master/scripts/disk_usage.py # Source: https://github.com/giampaolo/psutil/blob/master/scripts/disk_usage.py
@staticmethod @staticmethod
def _all_disk_usage(): def _all_disk_usage():
disk_data = [] disk_data = []
# print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type","Mount")) # print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type","Mount"))
for part in psutil.disk_partitions(all=False): for part in psutil.disk_partitions(all=False):
if helper.is_os_windows(): if Helpers.is_os_windows():
if 'cdrom' in part.opts or part.fstype == '': if "cdrom" in part.opts or part.fstype == "":
# skip cd-rom drives with no disk in it; they may raise # skip cd-rom drives with no disk in it; they may raise
# ENOENT, pop-up a Windows GUI error for a non-ready # ENOENT, pop-up a Windows GUI error for a non-ready
# partition or just hang. # partition or just hang.
@ -95,13 +96,13 @@ class Stats:
usage = psutil.disk_usage(part.mountpoint) usage = psutil.disk_usage(part.mountpoint)
disk_data.append( disk_data.append(
{ {
'device': part.device, "device": part.device,
'total': helper.human_readable_file_size(usage.total), "total": Helpers.human_readable_file_size(usage.total),
'used': helper.human_readable_file_size(usage.used), "used": Helpers.human_readable_file_size(usage.used),
'free': helper.human_readable_file_size(usage.free), "free": Helpers.human_readable_file_size(usage.free),
'percent_used': int(usage.percent), "percent_used": int(usage.percent),
'fs': part.fstype, "fs": part.fstype,
'mount': part.mountpoint "mount": part.mountpoint,
} }
) )
@ -112,15 +113,15 @@ class Stats:
total_size = 0 total_size = 0
total_size = helper.get_dir_size(server_path) total_size = Helpers.get_dir_size(server_path)
level_total_size = helper.human_readable_file_size(total_size) level_total_size = Helpers.human_readable_file_size(total_size)
return level_total_size return level_total_size
def get_server_players(self, server_id): def get_server_players(self, server_id):
server = servers_helper.get_server_data_by_id(server_id) server = HelperServers.get_server_data_by_id(server_id)
logger.info(f"Getting players for server {server}") logger.info(f"Getting players for server {server}")
@ -128,22 +129,20 @@ class Stats:
# server_settings = server.get('server_settings', {}) # server_settings = server.get('server_settings', {})
# server_data = server.get('server_data_obj', {}) # server_data = server.get('server_data_obj', {})
# TODO: search server properties file for possible override of 127.0.0.1 # TODO: search server properties file for possible override of 127.0.0.1
internal_ip = server['server_ip'] internal_ip = server["server_ip"]
server_port = server['server_port'] server_port = server["server_port"]
logger.debug("Pinging {internal_ip} on port {server_port}") logger.debug(f"Pinging {internal_ip} on port {server_port}")
if servers_helper.get_server_type_by_id(server_id) != 'minecraft-bedrock': if HelperServers.get_server_type_by_id(server_id) != "minecraft-bedrock":
int_mc_ping = ping(internal_ip, int(server_port)) int_mc_ping = ping(internal_ip, int(server_port))
ping_data = {} ping_data = {}
# if we got a good ping return, let's parse it # if we got a good ping return, let's parse it
if int_mc_ping: if int_mc_ping:
ping_data = Stats.parse_server_ping(int_mc_ping) ping_data = Stats.parse_server_ping(int_mc_ping)
return ping_data['players'] return ping_data["players"]
return [] return []
@staticmethod @staticmethod
@ -156,87 +155,64 @@ class Stats:
except Exception as e: except Exception as e:
logger.info(f"Unable to read json from ping_obj: {e}") logger.info(f"Unable to read json from ping_obj: {e}")
try: try:
server_icon = base64.encodebytes(ping_obj.icon) server_icon = base64.encodebytes(ping_obj.icon)
server_icon = server_icon.decode('utf-8') server_icon = server_icon.decode("utf-8")
except Exception as e: except Exception as e:
server_icon = False server_icon = False
logger.info(f"Unable to read the server icon : {e}") logger.info(f"Unable to read the server icon : {e}")
ping_data = { ping_data = {
'online': online_stats.get("online", 0), "online": online_stats.get("online", 0),
'max': online_stats.get('max', 0), "max": online_stats.get("max", 0),
'players': online_stats.get('players', 0), "players": online_stats.get("players", 0),
'server_description': ping_obj.description, "server_description": ping_obj.description,
'server_version': ping_obj.version, "server_version": ping_obj.version,
'server_icon': server_icon "server_icon": server_icon,
} }
return ping_data return ping_data
@staticmethod @staticmethod
def parse_server_RakNet_ping(ping_obj: object): def parse_server_raknet_ping(ping_obj: object):
try: try:
server_icon = base64.encodebytes(ping_obj['icon']) server_icon = base64.encodebytes(ping_obj["icon"])
except Exception as e: except Exception as e:
server_icon = False server_icon = False
logger.info(f"Unable to read the server icon : {e}") logger.info(f"Unable to read the server icon : {e}")
ping_data = { ping_data = {
'online': ping_obj['server_player_count'], "online": ping_obj["server_player_count"],
'max': ping_obj['server_player_max'], "max": ping_obj["server_player_max"],
'players': [], "players": [],
'server_description': ping_obj['server_edition'], "server_description": ping_obj["server_edition"],
'server_version': ping_obj['server_version_name'], "server_version": ping_obj["server_version_name"],
'server_icon': server_icon "server_icon": server_icon,
} }
return ping_data return ping_data
def record_stats(self): def record_stats(self):
stats_to_send = self.get_node_stats() stats_to_send = self.get_node_stats()
node_stats = stats_to_send.get('node_stats') node_stats = stats_to_send.get("node_stats")
Host_Stats.insert({ HostStats.insert(
Host_Stats.boot_time: node_stats.get('boot_time', "Unknown"), {
Host_Stats.cpu_usage: round(node_stats.get('cpu_usage', 0), 2), HostStats.boot_time: node_stats.get("boot_time", "Unknown"),
Host_Stats.cpu_cores: node_stats.get('cpu_count', 0), HostStats.cpu_usage: round(node_stats.get("cpu_usage", 0), 2),
Host_Stats.cpu_cur_freq: node_stats.get('cpu_cur_freq', 0), HostStats.cpu_cores: node_stats.get("cpu_count", 0),
Host_Stats.cpu_max_freq: node_stats.get('cpu_max_freq', 0), HostStats.cpu_cur_freq: node_stats.get("cpu_cur_freq", 0),
Host_Stats.mem_usage: node_stats.get('mem_usage', "0 MB"), HostStats.cpu_max_freq: node_stats.get("cpu_max_freq", 0),
Host_Stats.mem_percent: node_stats.get('mem_percent', 0), HostStats.mem_usage: node_stats.get("mem_usage", "0 MB"),
Host_Stats.mem_total: node_stats.get('mem_total', "0 MB"), HostStats.mem_percent: node_stats.get("mem_percent", 0),
Host_Stats.disk_json: node_stats.get('disk_data', '{}') HostStats.mem_total: node_stats.get("mem_total", "0 MB"),
}).execute() HostStats.disk_json: node_stats.get("disk_data", "{}"),
}
# server_stats = stats_to_send.get('servers')# ).execute()
#
# 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 # delete old data
max_age = helper.get_setting("history_max_age") max_age = self.helper.get_setting("history_max_age")
now = datetime.datetime.now() now = datetime.datetime.now()
last_week = now.day - max_age last_week = now.day - max_age
Host_Stats.delete().where(Host_Stats.time < last_week).execute() HostStats.delete().where(HostStats.time < last_week).execute()
# Server_Stats.delete().where(Server_Stats.created < last_week).execute()

View File

@ -0,0 +1,8 @@
import peewee
database_proxy = peewee.DatabaseProxy()
class BaseModel(peewee.Model):
class Meta:
database = database_proxy

View File

@ -1,29 +1,24 @@
import logging import logging
import typing
from enum import Enum
from peewee import (
ForeignKeyField,
CharField,
IntegerField,
DoesNotExist,
)
from app.classes.shared.helpers import helper from app.classes.models.base_model import BaseModel
from app.classes.shared.permission_helper import permission_helper from app.classes.models.users import Users, ApiKeys, HelperUsers
from app.classes.models.users import Users, ApiKeys from app.classes.shared.permission_helper import PermissionHelper
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__) logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
# **********************************************************************************
#************************************************************************************************
# User_Crafty Class # User_Crafty Class
#************************************************************************************************ # **********************************************************************************
class User_Crafty(Model): class UserCrafty(BaseModel):
user_id = ForeignKeyField(Users, backref='users_crafty') user_id = ForeignKeyField(Users, backref="users_crafty")
permissions = CharField(default="00000000") permissions = CharField(default="00000000")
limit_server_creation = IntegerField(default=-1) limit_server_creation = IntegerField(default=-1)
limit_user_creation = IntegerField(default=0) limit_user_creation = IntegerField(default=0)
@ -33,167 +28,212 @@ class User_Crafty(Model):
created_role = IntegerField(default=0) created_role = IntegerField(default=0)
class Meta: class Meta:
table_name = 'user_crafty' table_name = "user_crafty"
database = database
#************************************************************************************************
# **********************************************************************************
# Crafty Permissions Class # Crafty Permissions Class
#************************************************************************************************ # **********************************************************************************
class Enum_Permissions_Crafty(Enum): class EnumPermissionsCrafty(Enum):
Server_Creation = 0 SERVER_CREATION = 0
User_Config = 1 USER_CONFIG = 1
Roles_Config = 2 ROLES_CONFIG = 2
class Permissions_Crafty:
#************************************************************************************************ class PermissionsCrafty:
# **********************************************************************************
# Crafty Permissions Methods # Crafty Permissions Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_permissions_list(): def get_permissions_list():
permissions_list = [] permissions_list: typing.List[EnumPermissionsCrafty] = []
for member in Enum_Permissions_Crafty.__members__.items(): for member in EnumPermissionsCrafty.__members__.items():
permissions_list.append(member[1]) permissions_list.append(member[1])
return permissions_list return permissions_list
@staticmethod @staticmethod
def get_permissions(permissions_mask): def get_permissions(permissions_mask):
permissions_list = [] permissions_list: typing.List[EnumPermissionsCrafty] = []
for member in Enum_Permissions_Crafty.__members__.items(): for member in EnumPermissionsCrafty.__members__.items():
if crafty_permissions.has_permission(permissions_mask, member[1]): if PermissionsCrafty.has_permission(permissions_mask, member[1]):
permissions_list.append(member[1]) permissions_list.append(member[1])
return permissions_list return permissions_list
@staticmethod @staticmethod
def has_permission(permission_mask, permission_tested: Enum_Permissions_Crafty): def has_permission(
permission_mask: typing.Mapping[int, str],
permission_tested: EnumPermissionsCrafty,
):
result = False result = False
if permission_mask[permission_tested.value] == '1': if permission_mask[permission_tested.value] == "1":
result = True result = True
return result return result
@staticmethod @staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Crafty, value): def set_permission(
l = list(permission_mask) permission_mask, permission_tested: EnumPermissionsCrafty, value
l[permission_tested.value] = str(value) ):
permission_mask = ''.join(l) lst = list(permission_mask)
lst[permission_tested.value] = str(value)
permission_mask = "".join(lst)
return permission_mask return permission_mask
@staticmethod @staticmethod
def get_permission(permission_mask, permission_tested: Enum_Permissions_Crafty): def get_permission(permission_mask, permission_tested: EnumPermissionsCrafty):
return permission_mask[permission_tested.value] return permission_mask[permission_tested.value]
@staticmethod @staticmethod
def get_crafty_permissions_mask(user_id): def get_crafty_permissions_mask(user_id):
permissions_mask = '' permissions_mask = ""
user_crafty = crafty_permissions.get_User_Crafty(user_id) user_crafty = PermissionsCrafty.get_user_crafty(user_id)
permissions_mask = user_crafty.permissions permissions_mask = user_crafty.permissions
return permissions_mask return permissions_mask
@staticmethod @staticmethod
def get_all_permission_quantity_list(): def get_all_permission_quantity_list():
quantity_list = { quantity_list = {
Enum_Permissions_Crafty.Server_Creation.name: -1, EnumPermissionsCrafty.SERVER_CREATION.name: -1,
Enum_Permissions_Crafty.User_Config.name: -1, EnumPermissionsCrafty.USER_CONFIG.name: -1,
Enum_Permissions_Crafty.Roles_Config.name: -1, EnumPermissionsCrafty.ROLES_CONFIG.name: -1,
} }
return quantity_list return quantity_list
@staticmethod @staticmethod
def get_permission_quantity_list(user_id): def get_permission_quantity_list(user_id):
user_crafty = crafty_permissions.get_User_Crafty(user_id) user_crafty = PermissionsCrafty.get_user_crafty(user_id)
quantity_list = { quantity_list = {
Enum_Permissions_Crafty.Server_Creation.name: user_crafty.limit_server_creation, EnumPermissionsCrafty.SERVER_CREATION.name: user_crafty.limit_server_creation, # pylint: disable=line-too-long
Enum_Permissions_Crafty.User_Config.name: user_crafty.limit_user_creation, EnumPermissionsCrafty.USER_CONFIG.name: user_crafty.limit_user_creation,
Enum_Permissions_Crafty.Roles_Config.name: user_crafty.limit_role_creation, EnumPermissionsCrafty.ROLES_CONFIG.name: user_crafty.limit_role_creation,
} }
return quantity_list return quantity_list
#************************************************************************************************ # **********************************************************************************
# User_Crafty Methods # User_Crafty Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_User_Crafty(user_id): def get_user_crafty(user_id):
try: try:
user_crafty = User_Crafty.select().where(User_Crafty.user_id == user_id).get() user_crafty = UserCrafty.select().where(UserCrafty.user_id == user_id).get()
except DoesNotExist: except DoesNotExist:
user_crafty = User_Crafty.insert({ user_crafty = UserCrafty.insert(
User_Crafty.user_id: user_id, {
User_Crafty.permissions: "000", UserCrafty.user_id: user_id,
User_Crafty.limit_server_creation: 0, UserCrafty.permissions: "000",
User_Crafty.limit_user_creation: 0, UserCrafty.limit_server_creation: 0,
User_Crafty.limit_role_creation: 0, UserCrafty.limit_user_creation: 0,
User_Crafty.created_server: 0, UserCrafty.limit_role_creation: 0,
User_Crafty.created_user: 0, UserCrafty.created_server: 0,
User_Crafty.created_role: 0, UserCrafty.created_user: 0,
}).execute() UserCrafty.created_role: 0,
user_crafty = crafty_permissions.get_User_Crafty(user_id) }
).execute()
user_crafty = PermissionsCrafty.get_user_crafty(user_id)
return user_crafty return user_crafty
@staticmethod @staticmethod
def add_user_crafty(user_id, uc_permissions): def add_user_crafty(user_id, uc_permissions):
user_crafty = User_Crafty.insert({User_Crafty.user_id: user_id, User_Crafty.permissions: uc_permissions}).execute() user_crafty = UserCrafty.insert(
{UserCrafty.user_id: user_id, UserCrafty.permissions: uc_permissions}
).execute()
return user_crafty return user_crafty
@staticmethod @staticmethod
def add_or_update_user(user_id, permissions_mask, limit_server_creation, limit_user_creation, limit_role_creation): def add_or_update_user(
user_id,
permissions_mask,
limit_server_creation,
limit_user_creation,
limit_role_creation,
):
try: try:
user_crafty = User_Crafty.select().where(User_Crafty.user_id == user_id).get() user_crafty = UserCrafty.select().where(UserCrafty.user_id == user_id).get()
user_crafty.permissions = permissions_mask user_crafty.permissions = permissions_mask
user_crafty.limit_server_creation = limit_server_creation user_crafty.limit_server_creation = limit_server_creation
user_crafty.limit_user_creation = limit_user_creation user_crafty.limit_user_creation = limit_user_creation
user_crafty.limit_role_creation = limit_role_creation user_crafty.limit_role_creation = limit_role_creation
User_Crafty.save(user_crafty) UserCrafty.save(user_crafty)
except: except:
User_Crafty.insert({ UserCrafty.insert(
User_Crafty.user_id: user_id, {
User_Crafty.permissions: permissions_mask, UserCrafty.user_id: user_id,
User_Crafty.limit_server_creation: limit_server_creation, UserCrafty.permissions: permissions_mask,
User_Crafty.limit_user_creation: limit_user_creation, UserCrafty.limit_server_creation: limit_server_creation,
User_Crafty.limit_role_creation: limit_role_creation UserCrafty.limit_user_creation: limit_user_creation,
}).execute() UserCrafty.limit_role_creation: limit_role_creation,
}
).execute()
@staticmethod @staticmethod
def get_created_quantity_list(user_id): def get_created_quantity_list(user_id):
user_crafty = crafty_permissions.get_User_Crafty(user_id) user_crafty = PermissionsCrafty.get_user_crafty(user_id)
quantity_list = { quantity_list = {
Enum_Permissions_Crafty.Server_Creation.name: user_crafty.created_server, EnumPermissionsCrafty.SERVER_CREATION.name: user_crafty.created_server,
Enum_Permissions_Crafty.User_Config.name: user_crafty.created_user, EnumPermissionsCrafty.USER_CONFIG.name: user_crafty.created_user,
Enum_Permissions_Crafty.Roles_Config.name: user_crafty.created_role, EnumPermissionsCrafty.ROLES_CONFIG.name: user_crafty.created_role,
} }
return quantity_list return quantity_list
@staticmethod @staticmethod
def get_crafty_limit_value(user_id, permission): def get_crafty_limit_value(user_id, permission):
quantity_list = crafty_permissions.get_permission_quantity_list(user_id) quantity_list = PermissionsCrafty.get_permission_quantity_list(user_id)
return quantity_list[permission] return quantity_list[permission]
@staticmethod @staticmethod
def can_add_in_crafty(user_id, permission): def can_add_in_crafty(user_id, permission):
user_crafty = crafty_permissions.get_User_Crafty(user_id) user_crafty = PermissionsCrafty.get_user_crafty(user_id)
can = crafty_permissions.has_permission(user_crafty.permissions, permission) can = PermissionsCrafty.has_permission(user_crafty.permissions, permission)
limit_list = crafty_permissions.get_permission_quantity_list(user_id) limit_list = PermissionsCrafty.get_permission_quantity_list(user_id)
quantity_list = crafty_permissions.get_created_quantity_list(user_id) quantity_list = PermissionsCrafty.get_created_quantity_list(user_id)
return can and ((quantity_list[permission.name] < limit_list[permission.name]) or limit_list[permission.name] == -1 ) return can and (
(quantity_list[permission.name] < limit_list[permission.name])
or limit_list[permission.name] == -1
)
@staticmethod @staticmethod
def add_server_creation(user_id): def add_server_creation(user_id):
user_crafty = crafty_permissions.get_User_Crafty(user_id) """Increase the "Server Creation" counter for this user
Args:
user_id (int): The modifiable user's ID
Returns:
int: The new count of servers created by this user
"""
user_crafty = PermissionsCrafty.get_user_crafty(user_id)
user_crafty.created_server += 1 user_crafty.created_server += 1
User_Crafty.save(user_crafty) UserCrafty.save(user_crafty)
return user_crafty.created_server return user_crafty.created_server
@staticmethod
def add_user_creation(user_id):
user_crafty = PermissionsCrafty.get_user_crafty(user_id)
user_crafty.created_user += 1
UserCrafty.save(user_crafty)
return user_crafty.created_user
@staticmethod
def add_role_creation(user_id):
user_crafty = PermissionsCrafty.get_user_crafty(user_id)
user_crafty.created_role += 1
UserCrafty.save(user_crafty)
return user_crafty.created_role
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys): def get_api_key_permissions_list(key: ApiKeys):
user = key.user user = HelperUsers.get_user(key.user_id)
if user.superuser and key.superuser: if user["superuser"] and key.superuser:
return crafty_permissions.get_permissions_list() return PermissionsCrafty.get_permissions_list()
else: else:
user_permissions_mask = crafty_permissions.get_crafty_permissions_mask(user.user_id) if user["superuser"]:
user_permissions_mask = "111"
else:
user_permissions_mask = PermissionsCrafty.get_crafty_permissions_mask(
user["user_id"]
)
key_permissions_mask: str = key.crafty_permissions key_permissions_mask: str = key.crafty_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask) permissions_mask = PermissionHelper.combine_masks(
permissions_list = crafty_permissions.get_permissions(permissions_mask) user_permissions_mask, key_permissions_mask
)
permissions_list = PermissionsCrafty.get_permissions(permissions_mask)
return permissions_list return permissions_list
crafty_permissions = Permissions_Crafty()

View File

@ -1,47 +1,47 @@
import logging import logging
import datetime import datetime
from peewee import (
ForeignKeyField,
CharField,
IntegerField,
DateTimeField,
FloatField,
TextField,
AutoField,
BooleanField,
)
from playhouse.shortcuts import model_to_dict
from app.classes.models.users import Users, users_helper from app.classes.models.base_model import BaseModel
from app.classes.models.users import Users, HelperUsers
from app.classes.models.servers import Servers from app.classes.models.servers import Servers
from app.classes.models.server_permissions import server_permissions from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.helpers import helper from app.classes.shared.main_models import DatabaseShortcuts
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__) logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
#************************************************************************************************ # **********************************************************************************
# Audit_Log Class # Audit_Log Class
#************************************************************************************************ # **********************************************************************************
class Audit_Log(Model): class AuditLog(BaseModel):
audit_id = AutoField() audit_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
user_name = CharField(default="") user_name = CharField(default="")
user_id = IntegerField(default=0, index=True) user_id = IntegerField(default=0, index=True)
source_ip = CharField(default='127.0.0.1') source_ip = CharField(default="127.0.0.1")
server_id = IntegerField(default=None, index=True) # When auditing global events, use server ID 0 server_id = IntegerField(
log_msg = TextField(default='') default=None, index=True
) # When auditing global events, use server ID 0
log_msg = TextField(default="")
class Meta: class Meta:
database = database table_name = "audit_log"
#************************************************************************************************ # **********************************************************************************
# Host_Stats Class # Host_Stats Class
#************************************************************************************************ # **********************************************************************************
class Host_Stats(Model): class HostStats(BaseModel):
time = DateTimeField(default=datetime.datetime.now, index=True) time = DateTimeField(default=datetime.datetime.now, index=True)
boot_time = CharField(default="") boot_time = CharField(default="")
cpu_usage = FloatField(default=0) cpu_usage = FloatField(default=0)
@ -55,30 +55,28 @@ class Host_Stats(Model):
class Meta: class Meta:
table_name = "host_stats" table_name = "host_stats"
database = database
#************************************************************************************************ # **********************************************************************************
# Commands Class # Commands Class
#************************************************************************************************ # **********************************************************************************
class Commands(Model): class Commands(BaseModel):
command_id = AutoField() command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server', index=True) server_id = ForeignKeyField(Servers, backref="server", index=True)
user = ForeignKeyField(Users, backref='user', index=True) user = ForeignKeyField(Users, backref="user", index=True)
source_ip = CharField(default='127.0.0.1') source_ip = CharField(default="127.0.0.1")
command = CharField(default='') command = CharField(default="")
executed = BooleanField(default=False) executed = BooleanField(default=False)
class Meta: class Meta:
table_name = "commands" table_name = "commands"
database = database
#************************************************************************************************ # **********************************************************************************
# Webhooks Class # Webhooks Class
#************************************************************************************************ # **********************************************************************************
class Webhooks(Model): class Webhooks(BaseModel):
id = AutoField() id = AutoField()
name = CharField(max_length=64, unique=True, index=True) name = CharField(max_length=64, unique=True, index=True)
method = CharField(default="POST") method = CharField(default="POST")
@ -88,15 +86,14 @@ class Webhooks(Model):
class Meta: class Meta:
table_name = "webhooks" table_name = "webhooks"
database = database
#************************************************************************************************ # **********************************************************************************
# Schedules Class # Schedules Class
#************************************************************************************************ # **********************************************************************************
class Schedules(Model): class Schedules(BaseModel):
schedule_id = IntegerField(unique=True, primary_key=True) schedule_id = IntegerField(unique=True, primary_key=True)
server_id = ForeignKeyField(Servers, backref='schedule_server') server_id = ForeignKeyField(Servers, backref="schedule_server")
enabled = BooleanField() enabled = BooleanField()
action = CharField() action = CharField()
interval = IntegerField() interval = IntegerField()
@ -110,44 +107,49 @@ class Schedules(Model):
delay = IntegerField(default=0) delay = IntegerField(default=0)
class Meta: class Meta:
table_name = 'schedules' table_name = "schedules"
database = database
#************************************************************************************************ # **********************************************************************************
# Backups Class # Backups Class
#************************************************************************************************ # **********************************************************************************
class Backups(Model): class Backups(BaseModel):
excluded_dirs = CharField(null=True) excluded_dirs = CharField(null=True)
max_backups = IntegerField() max_backups = IntegerField()
server_id = ForeignKeyField(Servers, backref='backups_server') server_id = ForeignKeyField(Servers, backref="backups_server")
compress = BooleanField(default=False) compress = BooleanField(default=False)
class Meta: class Meta:
table_name = 'backups' table_name = "backups"
database = database
class helpers_management:
#************************************************************************************************ class HelpersManagement:
def __init__(self, database, helper):
self.database = database
self.helper = helper
# **********************************************************************************
# Host_Stats Methods # Host_Stats Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_latest_hosts_stats(): def get_latest_hosts_stats():
#pylint: disable=no-member # pylint: disable=no-member
query = Host_Stats.select().order_by(Host_Stats.id.desc()).get() query = HostStats.select().order_by(HostStats.id.desc()).get()
return model_to_dict(query) return model_to_dict(query)
#************************************************************************************************ # **********************************************************************************
# Commands Methods # Commands Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def add_command(server_id, user_id, remote_ip, command): def add_command(server_id, user_id, remote_ip, command):
Commands.insert({ Commands.insert(
Commands.server_id: server_id, {
Commands.user: user_id, Commands.server_id: server_id,
Commands.source_ip: remote_ip, Commands.user: user_id,
Commands.command: command Commands.source_ip: remote_ip,
}).execute() Commands.command: command,
}
).execute()
@staticmethod @staticmethod
def get_unactioned_commands(): def get_unactioned_commands():
@ -158,72 +160,80 @@ class helpers_management:
def mark_command_complete(command_id=None): def mark_command_complete(command_id=None):
if command_id is not None: if command_id is not None:
logger.debug(f"Marking Command {command_id} completed") logger.debug(f"Marking Command {command_id} completed")
Commands.update({ Commands.update({Commands.executed: True}).where(
Commands.executed: True Commands.command_id == command_id
}).where(Commands.command_id == command_id).execute() ).execute()
#************************************************************************************************ # **********************************************************************************
# Audit_Log Methods # Audit_Log Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_actity_log(): def get_actity_log():
q = Audit_Log.select() query = AuditLog.select()
return db_helper.return_db_rows(q) return DatabaseShortcuts.return_db_rows(query)
@staticmethod def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
def add_to_audit_log(user_id, log_msg, server_id=None, source_ip=None):
logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ") logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ")
user_data = users_helper.get_user(user_id) user_data = HelperUsers.get_user(user_id)
audit_msg = f"{str(user_data['username']).capitalize()} {log_msg}" audit_msg = f"{str(user_data['username']).capitalize()} {log_msg}"
server_users = server_permissions.get_server_user_list(server_id) server_users = PermissionsServers.get_server_user_list(server_id)
for user in server_users: for user in server_users:
websocket_helper.broadcast_user(user,'notification', audit_msg) try:
self.helper.websocket_helper.broadcast_user(
user, "notification", audit_msg
)
except Exception as e:
logger.error(f"Error broadcasting to user {user} - {e}")
Audit_Log.insert({ AuditLog.insert(
Audit_Log.user_name: user_data['username'], {
Audit_Log.user_id: user_id, AuditLog.user_name: user_data["username"],
Audit_Log.server_id: server_id, AuditLog.user_id: user_id,
Audit_Log.log_msg: audit_msg, AuditLog.server_id: server_id,
Audit_Log.source_ip: source_ip AuditLog.log_msg: audit_msg,
}).execute() AuditLog.source_ip: source_ip,
#deletes records when they're more than 100 }
ordered = Audit_Log.select().order_by(+Audit_Log.created) ).execute()
# deletes records when there's more than 300
ordered = AuditLog.select().order_by(+AuditLog.created)
for item in ordered: for item in ordered:
if not helper.get_setting('max_audit_entries'): if not self.helper.get_setting("max_audit_entries"):
max_entries = 300 max_entries = 300
else: else:
max_entries = helper.get_setting('max_audit_entries') max_entries = self.helper.get_setting("max_audit_entries")
if Audit_Log.select().count() > max_entries: if AuditLog.select().count() > max_entries:
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute() AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
else: else:
return return
@staticmethod def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip):
def add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip): AuditLog.insert(
Audit_Log.insert({ {
Audit_Log.user_name: user_name, AuditLog.user_name: user_name,
Audit_Log.user_id: user_id, AuditLog.user_id: user_id,
Audit_Log.server_id: server_id, AuditLog.server_id: server_id,
Audit_Log.log_msg: log_msg, AuditLog.log_msg: log_msg,
Audit_Log.source_ip: source_ip AuditLog.source_ip: source_ip,
}).execute() }
#deletes records when they're more than 100 ).execute()
ordered = Audit_Log.select().order_by(+Audit_Log.created) # deletes records when there's more than 300
ordered = AuditLog.select().order_by(+AuditLog.created)
for item in ordered: for item in ordered:
#configurable through app/config/config.json # configurable through app/config/config.json
if not helper.get_setting('max_audit_entries'): if not self.helper.get_setting("max_audit_entries"):
max_entries = 300 max_entries = 300
else: else:
max_entries = helper.get_setting('max_audit_entries') max_entries = self.helper.get_setting("max_audit_entries")
if Audit_Log.select().count() > max_entries: if AuditLog.select().count() > max_entries:
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute() AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
else: else:
return return
#************************************************************************************************
# **********************************************************************************
# Schedules Methods # Schedules Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def create_scheduled_task( def create_scheduled_task(
server_id, server_id,
@ -235,24 +245,26 @@ class helpers_management:
comment=None, comment=None,
enabled=True, enabled=True,
one_time=False, one_time=False,
cron_string='* * * * *', cron_string="* * * * *",
parent=None, parent=None,
delay=0): delay=0,
sch_id = Schedules.insert({ ):
Schedules.server_id: server_id, sch_id = Schedules.insert(
Schedules.action: action, {
Schedules.enabled: enabled, Schedules.server_id: server_id,
Schedules.interval: interval, Schedules.action: action,
Schedules.interval_type: interval_type, Schedules.enabled: enabled,
Schedules.start_time: start_time, Schedules.interval: interval,
Schedules.command: command, Schedules.interval_type: interval_type,
Schedules.comment: comment, Schedules.start_time: start_time,
Schedules.one_time: one_time, Schedules.command: command,
Schedules.cron_string: cron_string, Schedules.comment: comment,
Schedules.parent: parent, Schedules.one_time: one_time,
Schedules.delay: delay Schedules.cron_string: cron_string,
Schedules.parent: parent,
}).execute() Schedules.delay: delay,
}
).execute()
return sch_id return sch_id
@staticmethod @staticmethod
@ -282,7 +294,11 @@ class helpers_management:
@staticmethod @staticmethod
def get_child_schedules_by_server(schedule_id, server_id): def get_child_schedules_by_server(schedule_id, server_id):
return Schedules.select().where(Schedules.server_id == server_id, Schedules.parent == schedule_id).execute() return (
Schedules.select()
.where(Schedules.server_id == server_id, Schedules.parent == schedule_id)
.execute()
)
@staticmethod @staticmethod
def get_child_schedules(schedule_id): def get_child_schedules(schedule_id):
@ -294,22 +310,27 @@ class helpers_management:
@staticmethod @staticmethod
def get_schedules_enabled(): def get_schedules_enabled():
#pylint: disable=singleton-comparison return (
return Schedules.select().where(Schedules.enabled == True).execute() Schedules.select()
.where(Schedules.enabled == True) # pylint: disable=singleton-comparison
.execute()
)
#************************************************************************************************ # **********************************************************************************
# Backups Methods # Backups Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_backup_config(server_id): def get_backup_config(server_id):
try: try:
row = Backups.select().where(Backups.server_id == server_id).join(Servers)[0] row = (
Backups.select().where(Backups.server_id == server_id).join(Servers)[0]
)
conf = { conf = {
"backup_path": row.server_id.backup_path, "backup_path": row.server_id.backup_path,
"excluded_dirs": row.excluded_dirs, "excluded_dirs": row.excluded_dirs,
"max_backups": row.max_backups, "max_backups": row.max_backups,
"server_id": row.server_id.server_id, "server_id": row.server_id.server_id,
"compress": row.compress "compress": row.compress,
} }
except IndexError: except IndexError:
conf = { conf = {
@ -321,8 +342,14 @@ class helpers_management:
} }
return conf return conf
@staticmethod def set_backup_config(
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None, compress: bool = False): self,
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()}") logger.debug(f"Updating server {server_id} backup config with {locals()}")
if Backups.select().where(Backups.server_id == server_id).count() != 0: if Backups.select().where(Backups.server_id == server_id).count() != 0:
new_row = False new_row = False
@ -331,34 +358,46 @@ class helpers_management:
conf = { conf = {
"excluded_dirs": None, "excluded_dirs": None,
"max_backups": 0, "max_backups": 0,
"server_id": server_id, "server_id": server_id,
"compress": False "compress": False,
} }
new_row = True new_row = True
if max_backups is not None: if max_backups is not None:
conf['max_backups'] = max_backups conf["max_backups"] = max_backups
if excluded_dirs is not None: if excluded_dirs is not None:
dirs_to_exclude = ",".join(excluded_dirs) dirs_to_exclude = ",".join(excluded_dirs)
conf['excluded_dirs'] = dirs_to_exclude conf["excluded_dirs"] = dirs_to_exclude
conf['compress'] = compress conf["compress"] = compress
if not new_row: if not new_row:
with database.atomic(): with self.database.atomic():
if backup_path is not None: if backup_path is not None:
u1 = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id).execute() server_rows = (
Servers.update(backup_path=backup_path)
.where(Servers.server_id == server_id)
.execute()
)
else: else:
u1 = 0 server_rows = 0
u2 = Backups.update(conf).where(Backups.server_id == server_id).execute() backup_rows = (
logger.debug(f"Updating existing backup record. {u1}+{u2} rows affected") Backups.update(conf).where(Backups.server_id == server_id).execute()
)
logger.debug(
f"Updating existing backup record. "
f"{server_rows}+{backup_rows} rows affected"
)
else: else:
with database.atomic(): with self.database.atomic():
conf["server_id"] = server_id conf["server_id"] = server_id
if backup_path is not None: if backup_path is not None:
Servers.update(backup_path=backup_path).where(Servers.server_id == server_id) Servers.update(backup_path=backup_path).where(
Servers.server_id == server_id
)
Backups.create(**conf) Backups.create(**conf)
logger.debug("Creating new backup record.") logger.debug("Creating new backup record.")
def get_excluded_backup_dirs(self, server_id: int): @staticmethod
excluded_dirs = self.get_backup_config(server_id)['excluded_dirs'] def get_excluded_backup_dirs(server_id: int):
excluded_dirs = HelpersManagement.get_backup_config(server_id)["excluded_dirs"]
if excluded_dirs is not None and excluded_dirs != "": if excluded_dirs is not None and excluded_dirs != "":
dir_list = excluded_dirs.split(",") dir_list = excluded_dirs.split(",")
else: else:
@ -366,29 +405,31 @@ class helpers_management:
return dir_list return dir_list
def add_excluded_backup_dir(self, server_id: int, dir_to_add: str): def add_excluded_backup_dir(self, server_id: int, dir_to_add: str):
dir_list = self.get_excluded_backup_dirs() dir_list = self.get_excluded_backup_dirs(server_id)
if dir_to_add not in dir_list: if dir_to_add not in dir_list:
dir_list.append(dir_to_add) dir_list.append(dir_to_add)
excluded_dirs = ",".join(dir_list) excluded_dirs = ",".join(dir_list)
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
else: else:
logger.debug(f"Not adding {dir_to_add} to excluded directories - already in the excluded directory list for server ID {server_id}") logger.debug(
f"Not adding {dir_to_add} to excluded directories - "
f"already in the excluded directory list for server ID {server_id}"
)
def del_excluded_backup_dir(self, server_id: int, dir_to_del: str): def del_excluded_backup_dir(self, server_id: int, dir_to_del: str):
dir_list = self.get_excluded_backup_dirs() dir_list = self.get_excluded_backup_dirs(server_id)
if dir_to_del in dir_list: if dir_to_del in dir_list:
dir_list.remove(dir_to_del) dir_list.remove(dir_to_del)
excluded_dirs = ",".join(dir_list) excluded_dirs = ",".join(dir_list)
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
else: else:
logger.debug(f"Not removing {dir_to_del} from excluded directories - not in the excluded directory list for server ID {server_id}") logger.debug(
f"Not removing {dir_to_del} from excluded directories - "
f"not in the excluded directory list for server ID {server_id}"
)
@staticmethod @staticmethod
def clear_unexecuted_commands(): def clear_unexecuted_commands():
Commands.update({ Commands.update({Commands.executed: True}).where(
Commands.executed: True Commands.executed == False # pylint: disable=singleton-comparison
#pylint: disable=singleton-comparison ).execute()
}).where(Commands.executed == False).execute()
management_helper = helpers_management()

View File

@ -1,26 +1,23 @@
import logging import logging
import datetime import datetime
import typing as t
from peewee import (
CharField,
DoesNotExist,
AutoField,
DateTimeField,
)
from playhouse.shortcuts import model_to_dict
from app.classes.shared.helpers import helper from app.classes.models.base_model import BaseModel
from app.classes.shared.helpers import Helpers
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__) logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
#************************************************************************************************ # **********************************************************************************
# Roles Class # Roles Class
#************************************************************************************************ # **********************************************************************************
class Roles(Model): class Roles(BaseModel):
role_id = AutoField() role_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
last_update = DateTimeField(default=datetime.datetime.now) last_update = DateTimeField(default=datetime.datetime.now)
@ -28,16 +25,22 @@ class Roles(Model):
class Meta: class Meta:
table_name = "roles" table_name = "roles"
database = database
#************************************************************************************************
# **********************************************************************************
# Roles Helpers # Roles Helpers
#************************************************************************************************ # **********************************************************************************
class helper_roles: class HelperRoles:
def __init__(self, database):
self.database = database
@staticmethod @staticmethod
def get_all_roles(): def get_all_roles():
query = Roles.select() return Roles.select()
return query
@staticmethod
def get_all_role_ids() -> t.List[int]:
return [role.role_id for role in Roles.select(Roles.role_id).execute()]
@staticmethod @staticmethod
def get_roleid_by_name(role_name): def get_roleid_by_name(role_name):
@ -50,28 +53,43 @@ class helper_roles:
def get_role(role_id): def get_role(role_id):
return model_to_dict(Roles.get(Roles.role_id == role_id)) return model_to_dict(Roles.get(Roles.role_id == role_id))
@staticmethod
def get_role_columns(
role_id: t.Union[str, int], column_names: t.List[str]
) -> t.List[t.Any]:
columns = [getattr(Roles, column) for column in column_names]
return model_to_dict(
Roles.select(*columns).where(Roles.role_id == role_id).get(),
only=columns,
)
@staticmethod
def get_role_column(role_id: t.Union[str, int], column_name: str) -> t.Any:
column = getattr(Roles, column_name)
return model_to_dict(
Roles.select(column).where(Roles.role_id == role_id).get(),
only=[column],
)[column_name]
@staticmethod @staticmethod
def add_role(role_name): def add_role(role_name):
role_id = Roles.insert({ role_id = Roles.insert(
Roles.role_name: role_name.lower(), {
Roles.created: helper.get_time_as_string() Roles.role_name: role_name.lower(),
}).execute() Roles.created: Helpers.get_time_as_string(),
}
).execute()
return role_id return role_id
@staticmethod @staticmethod
def update_role(role_id, up_data): def update_role(role_id, up_data):
return Roles.update(up_data).where(Roles.role_id == role_id).execute() return Roles.update(up_data).where(Roles.role_id == role_id).execute()
@staticmethod def remove_role(self, role_id):
def remove_role(role_id): return Roles.delete().where(Roles.role_id == role_id).execute()
with database.atomic():
role = Roles.get(Roles.role_id == role_id)
return role.delete_instance()
@staticmethod @staticmethod
def role_id_exists(role_id): def role_id_exists(role_id) -> bool:
if not roles_helper.get_role(role_id): if not HelperRoles.get_role(role_id):
return False return False
return True return True
roles_helper = helper_roles()

View File

@ -1,184 +1,242 @@
import logging import logging
import typing as t
from enum import Enum
from peewee import (
ForeignKeyField,
CharField,
CompositeKey,
JOIN,
)
from app.classes.models.base_model import BaseModel
from app.classes.models.servers import Servers from app.classes.models.servers import Servers
from app.classes.models.roles import Roles from app.classes.models.roles import Roles
from app.classes.models.users import User_Roles, users_helper, ApiKeys, Users from app.classes.models.users import UserRoles, HelperUsers, ApiKeys, Users
from app.classes.shared.helpers import helper from app.classes.shared.permission_helper import PermissionHelper
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__) logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
#************************************************************************************************ # **********************************************************************************
# Role Servers Class # Role Servers Class
#************************************************************************************************ # **********************************************************************************
class Role_Servers(Model): class RoleServers(BaseModel):
role_id = ForeignKeyField(Roles, backref='role_server') role_id = ForeignKeyField(Roles, backref="role_server")
server_id = ForeignKeyField(Servers, backref='role_server') server_id = ForeignKeyField(Servers, backref="role_server")
permissions = CharField(default="00000000") permissions = CharField(default="00000000")
class Meta: class Meta:
table_name = 'role_servers' table_name = "role_servers"
primary_key = CompositeKey('role_id', 'server_id') primary_key = CompositeKey("role_id", "server_id")
database = database
#************************************************************************************************
# **********************************************************************************
# Servers Permissions Class # Servers Permissions Class
#************************************************************************************************ # **********************************************************************************
class Enum_Permissions_Server(Enum): class EnumPermissionsServer(Enum):
Commands = 0 COMMANDS = 0
Terminal = 1 TERMINAL = 1
Logs = 2 LOGS = 2
Schedule = 3 SCHEDULE = 3
Backup = 4 BACKUP = 4
Files = 5 FILES = 5
Config = 6 CONFIG = 6
Players = 7 PLAYERS = 7
class Permissions_Servers:
class PermissionsServers:
@staticmethod @staticmethod
def get_or_create(role_id, server, permissions_mask): def get_or_create(role_id, server, permissions_mask):
return Role_Servers.get_or_create(role_id=role_id, server_id=server, permissions=permissions_mask) return RoleServers.get_or_create(
role_id=role_id, server_id=server, permissions=permissions_mask
)
@staticmethod @staticmethod
def get_permissions_list(): def get_permissions_list():
permissions_list = [] permissions_list: t.List[EnumPermissionsServer] = []
for member in Enum_Permissions_Server.__members__.items(): for member in EnumPermissionsServer.__members__.items():
permissions_list.append(member[1]) permissions_list.append(member[1])
return permissions_list return permissions_list
@staticmethod @staticmethod
def get_permissions(permissions_mask): def get_permissions(permissions_mask):
permissions_list = [] permissions_list: t.List[EnumPermissionsServer] = []
for member in Enum_Permissions_Server.__members__.items(): for member in EnumPermissionsServer.__members__.items():
if server_permissions.has_permission(permissions_mask, member[1]): if PermissionsServers.has_permission(permissions_mask, member[1]):
permissions_list.append(member[1]) permissions_list.append(member[1])
return permissions_list return permissions_list
@staticmethod @staticmethod
def has_permission(permission_mask, permission_tested: Enum_Permissions_Server): def has_permission(permission_mask, permission_tested: EnumPermissionsServer):
return permission_mask[permission_tested.value] == '1' return permission_mask[permission_tested.value] == "1"
@staticmethod @staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value): def set_permission(
permission_mask, permission_tested: EnumPermissionsServer, value
):
list_perms = list(permission_mask) list_perms = list(permission_mask)
list_perms[permission_tested.value] = str(value) list_perms[permission_tested.value] = str(value)
permission_mask = ''.join(list_perms) permission_mask = "".join(list_perms)
return permission_mask return permission_mask
@staticmethod @staticmethod
def get_permission(permission_mask, permission_tested: Enum_Permissions_Server): def get_permission(permission_mask, permission_tested: EnumPermissionsServer):
return permission_mask[permission_tested.value] return permission_mask[permission_tested.value]
@staticmethod @staticmethod
def get_token_permissions(permissions_mask, api_permissions_mask): def get_token_permissions(permissions_mask, api_permissions_mask):
permissions_list = [] permissions_list = []
for member in Enum_Permissions_Server.__members__.items(): for member in EnumPermissionsServer.__members__.items():
if permission_helper.both_have_perm(permissions_mask, api_permissions_mask, member[1]): if PermissionHelper.both_have_perm(
permissions_mask, api_permissions_mask, member[1]
):
permissions_list.append(member[1]) permissions_list.append(member[1])
return permissions_list return permissions_list
# **********************************************************************************
#************************************************************************************************
# Role_Servers Methods # Role_Servers Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_role_servers_from_role_id(roleid): def get_role_servers_from_role_id(roleid: t.Union[str, int]):
return Role_Servers.select().where(Role_Servers.role_id == roleid) return RoleServers.select().where(RoleServers.role_id == roleid)
@staticmethod @staticmethod
def get_servers_from_role(role_id): def get_servers_from_role(role_id: t.Union[str, int]):
return Role_Servers.select().join(Servers, JOIN.INNER).where(Role_Servers.role_id == role_id) return (
RoleServers.select()
.join(Servers, JOIN.INNER)
.where(RoleServers.role_id == role_id)
)
@staticmethod
def get_server_ids_from_role(role_id: t.Union[str, int]) -> t.List[int]:
# FIXME: somehow retrieve only the server ids, not the whole servers
return [
role_servers.server_id.server_id
for role_servers in (
RoleServers.select(RoleServers.server_id).where(
RoleServers.role_id == role_id
)
)
]
@staticmethod @staticmethod
def get_roles_from_server(server_id): def get_roles_from_server(server_id):
return Role_Servers.select().join(Roles, JOIN.INNER).where(Role_Servers.server_id == server_id) return (
RoleServers.select()
.join(Roles, JOIN.INNER)
.where(RoleServers.server_id == server_id)
)
@staticmethod @staticmethod
def add_role_server(server_id, role_id, rs_permissions="00000000"): 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, servers = RoleServers.insert(
Role_Servers.permissions: rs_permissions}).execute() {
RoleServers.server_id: server_id,
RoleServers.role_id: role_id,
RoleServers.permissions: rs_permissions,
}
).execute()
return servers return servers
@staticmethod @staticmethod
def get_permissions_mask(role_id, server_id): def get_permissions_mask(role_id, server_id):
permissions_mask = '' permissions_mask = ""
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).get() role_server = (
RoleServers.select()
.where(RoleServers.role_id == role_id)
.where(RoleServers.server_id == server_id)
.get()
)
permissions_mask = role_server.permissions permissions_mask = role_server.permissions
return permissions_mask return permissions_mask
@staticmethod @staticmethod
def get_server_roles(server_id): def get_server_roles(server_id):
role_list = [] role_list = []
roles = Role_Servers.select().where(Role_Servers.server_id == server_id).execute() roles = RoleServers.select().where(RoleServers.server_id == server_id).execute()
for role in roles: for role in roles:
role_list.append(role.role_id) role_list.append(role.role_id)
return role_list return role_list
@staticmethod @staticmethod
def get_role_permissions_list(role_id): def get_role_permissions_list(role_id):
permissions_mask = '00000000' permissions_mask = "00000000"
role_server = Role_Servers.get_or_none(Role_Servers.role_id == role_id) role_server = RoleServers.get_or_none(RoleServers.role_id == role_id)
if role_server is not None: if role_server is not None:
permissions_mask = role_server.permissions permissions_mask = role_server.permissions
permissions_list = server_permissions.get_permissions(permissions_mask) permissions_list = PermissionsServers.get_permissions(permissions_mask)
return permissions_list return permissions_list
@staticmethod @staticmethod
def update_role_permission(role_id, server_id, permissions_mask): def get_role_permissions_dict(role_id):
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).get() permissions_dict: t.Dict[str, t.List[EnumPermissionsServer]] = {}
role_server.permissions = permissions_mask role_servers = RoleServers.select(
Role_Servers.save(role_server) RoleServers.server_id, RoleServers.permissions
).where(RoleServers.role_id == role_id)
for role_server in role_servers:
permissions_dict[
role_server.server_id_id
] = PermissionsServers.get_permissions(role_server.permissions)
return permissions_dict
@staticmethod @staticmethod
def delete_roles_permissions(role_id, removed_servers=None): def update_role_permission(role_id, server_id, permissions_mask):
if removed_servers is None: role_server = (
removed_servers = {} RoleServers.select()
return Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute() .where(RoleServers.role_id == role_id)
.where(RoleServers.server_id == server_id)
.get()
)
role_server.permissions = permissions_mask
RoleServers.save(role_server)
@staticmethod
def delete_roles_permissions(
role_id: t.Union[str, int], removed_servers: t.Sequence[t.Union[str, int]]
):
return (
RoleServers.delete()
.where(RoleServers.role_id == role_id)
.where(RoleServers.server_id.in_(removed_servers))
.execute()
)
@staticmethod @staticmethod
def remove_roles_of_server(server_id): def remove_roles_of_server(server_id):
with database.atomic(): return RoleServers.delete().where(RoleServers.server_id == server_id).execute()
return Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
@staticmethod @staticmethod
def get_user_id_permissions_mask(user_id, server_id: str): def get_user_id_permissions_mask(user_id, server_id: str):
user = users_helper.get_user_model(user_id) user = HelperUsers.get_user_model(user_id)
return server_permissions.get_user_permissions_mask(user, server_id) return PermissionsServers.get_user_permissions_mask(user, server_id)
@staticmethod @staticmethod
def get_user_permissions_mask(user: Users, server_id: str): def get_user_permissions_mask(user: Users, server_id: str):
if user.superuser: if user.superuser:
permissions_mask = '1' * len(server_permissions.get_permissions_list()) permissions_mask = "1" * len(PermissionsServers.get_permissions_list())
else: else:
roles_list = users_helper.get_user_roles_id(user.user_id) roles_list = HelperUsers.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() role_server = (
RoleServers.select()
.where(RoleServers.role_id.in_(roles_list))
.where(RoleServers.server_id == server_id)
.execute()
)
try: try:
permissions_mask = role_server[0].permissions permissions_mask = role_server[0].permissions
except IndexError: except IndexError:
permissions_mask = '0' * len(server_permissions.get_permissions_list()) permissions_mask = "0" * len(PermissionsServers.get_permissions_list())
return permissions_mask return permissions_mask
@staticmethod @staticmethod
def get_server_user_list(server_id): def get_server_user_list(server_id):
final_users = [] final_users = []
server_roles = Role_Servers.select().where(Role_Servers.server_id == server_id) server_roles = RoleServers.select().where(RoleServers.server_id == server_id)
# pylint: disable=singleton-comparison super_users = Users.select().where(
super_users = Users.select().where(Users.superuser == True) Users.superuser == True # pylint: disable=singleton-comparison
)
for role in server_roles: for role in server_roles:
users = User_Roles.select().where(User_Roles.role_id == role.role_id) users = UserRoles.select().where(UserRoles.role_id == role.role_id)
for user in users: for user in users:
if user.user_id.user_id not in final_users: if user.user_id.user_id not in final_users:
final_users.append(user.user_id.user_id) final_users.append(user.user_id.user_id)
@ -189,36 +247,48 @@ class Permissions_Servers:
@staticmethod @staticmethod
def get_user_id_permissions_list(user_id, server_id: str): def get_user_id_permissions_list(user_id, server_id: str):
user = users_helper.get_user_model(user_id) user = HelperUsers.get_user_model(user_id)
return server_permissions.get_user_permissions_list(user, server_id) return PermissionsServers.get_user_permissions_list(user, server_id)
@staticmethod @staticmethod
def get_user_permissions_list(user: Users, server_id: str): def get_user_permissions_list(user: Users, server_id: str):
if user.superuser: if user.superuser:
permissions_list = server_permissions.get_permissions_list() permissions_list = PermissionsServers.get_permissions_list()
else: else:
permissions_mask = server_permissions.get_user_permissions_mask(user, server_id) permissions_mask = PermissionsServers.get_user_permissions_mask(
permissions_list = server_permissions.get_permissions(permissions_mask) user, server_id
)
permissions_list = PermissionsServers.get_permissions(permissions_mask)
return permissions_list return permissions_list
@staticmethod @staticmethod
def get_api_key_id_permissions_list(key_id, server_id: str): def get_api_key_id_permissions_list(key_id, server_id: str):
key = ApiKeys.get(ApiKeys.token_id == key_id) key = ApiKeys.get(ApiKeys.token_id == key_id)
return server_permissions.get_api_key_permissions_list(key, server_id) return PermissionsServers.get_api_key_permissions_list(key, server_id)
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str): def get_api_key_permissions_list(key: ApiKeys, server_id: str):
user = key.user user = HelperUsers.get_user(key.user_id)
if user.superuser and key.superuser: if user["superuser"] and key.superuser:
return server_permissions.get_permissions_list() return PermissionsServers.get_permissions_list()
else: else:
roles_list = users_helper.get_user_roles_id(user['user_id']) roles_list = HelperUsers.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() role_server = (
user_permissions_mask = role_server[0].permissions RoleServers.select()
.where(RoleServers.role_id.in_(roles_list))
.where(RoleServers.server_id == server_id)
.execute()
)
try:
user_permissions_mask = role_server[0].permissions
except:
if user["superuser"]:
user_permissions_mask = "11111111"
else:
user_permissions_mask = "00000000"
key_permissions_mask = key.server_permissions key_permissions_mask = key.server_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask) permissions_mask = PermissionHelper.combine_masks(
permissions_list = server_permissions.get_permissions(permissions_mask) user_permissions_mask, key_permissions_mask
)
permissions_list = PermissionsServers.get_permissions(permissions_mask)
return permissions_list return permissions_list
server_permissions = Permissions_Servers()

View File

@ -0,0 +1,355 @@
import os
import logging
import datetime
from app.classes.models.servers import Servers, HelperServers
from app.classes.shared.helpers import Helpers
from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.shared.migration import MigrationManager
try:
from peewee import (
SqliteDatabase,
Model,
DatabaseProxy,
ForeignKeyField,
CharField,
AutoField,
DateTimeField,
BooleanField,
IntegerField,
FloatField,
)
except ModuleNotFoundError as e:
Helpers.auto_installer_fix(e)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database_stats_proxy = DatabaseProxy()
# **********************************************************************************
# Servers Stats Class
# **********************************************************************************
class ServerStats(Model):
stats_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref="server", index=True)
started = CharField(default="")
running = BooleanField(default=False)
cpu = FloatField(default=0)
mem = FloatField(default=0)
mem_percent = FloatField(default=0)
world_name = CharField(default="")
world_size = CharField(default="")
server_port = IntegerField(default=25565)
int_ping_results = CharField(default="")
online = IntegerField(default=0)
max = IntegerField(default=0)
players = CharField(default="")
desc = CharField(default="Unable to Connect")
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:
table_name = "server_stats"
database = database_stats_proxy
# **********************************************************************************
# Servers_Stats Methods
# **********************************************************************************
class HelperServerStats:
def __init__(self, database):
self.database = database
@staticmethod
def init_database(server_id):
try:
server = HelperServers.get_server_data_by_id(server_id)
db_folder = os.path.join(f"{server['path']}", "db_stats")
db_file = os.path.join(
db_folder,
"crafty_server_stats.sqlite",
)
database = SqliteDatabase(
db_file, pragmas={"journal_mode": "wal", "cache_size": -1024 * 10}
)
if not os.path.exists(db_file):
try:
os.mkdir(db_folder)
except Exception as ex:
logger.warning(
f"Error try to create the db_stats folder for server : {ex}"
)
helper_stats = Helpers()
helper_stats.migration_dir = os.path.join(
f"{helper_stats.migration_dir}", "stats"
)
helper_stats.db_path = db_file
database_stats_proxy.initialize(database)
migration_manager = MigrationManager(database, helper_stats)
migration_manager.up() # Automatically runs migrations
database_stats_proxy.initialize(database)
except Exception as ex:
logger.warning(
f"Error try to look for the db_stats files for server : {ex}"
)
@staticmethod
def select_database(server_id):
try:
server = HelperServers.get_server_data_by_id(server_id)
db_file = os.path.join(
f"{server['path']}",
"db_stats",
"crafty_server_stats.sqlite",
)
database = SqliteDatabase(
db_file, pragmas={"journal_mode": "wal", "cache_size": -1024 * 10}
)
database_stats_proxy.initialize(database)
except Exception as ex:
logger.warning(
f"Error try to look for the db_stats files for server : {ex}"
)
@staticmethod
def get_all_servers_stats():
servers = HelperServers.get_all_defined_servers()
server_data = []
try:
for s in servers:
HelperServerStats.select_database(s.get("server_id"))
latest = (
ServerStats.select()
.where(ServerStats.server_id == s.get("server_id"))
.order_by(ServerStats.created.desc())
.limit(1)
)
server_data.append(
{
"server_data": s,
"stats": DatabaseShortcuts.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 insert_server_stats(server):
server_id = server.get("id", 0)
HelperServerStats.select_database(server_id)
if server_id == 0:
logger.warning("Stats saving failed with error: Server unknown (id = 0)")
return
ServerStats.insert(
{
ServerStats.server_id: server.get("id", 0),
ServerStats.started: server.get("started", ""),
ServerStats.running: server.get("running", False),
ServerStats.cpu: server.get("cpu", 0),
ServerStats.mem: server.get("mem", 0),
ServerStats.mem_percent: server.get("mem_percent", 0),
ServerStats.world_name: server.get("world_name", ""),
ServerStats.world_size: server.get("world_size", ""),
ServerStats.server_port: server.get("server_port", ""),
ServerStats.int_ping_results: server.get("int_ping_results", False),
ServerStats.online: server.get("online", False),
ServerStats.max: server.get("max", False),
ServerStats.players: server.get("players", False),
ServerStats.desc: server.get("desc", False),
ServerStats.version: server.get("version", False),
}
).execute()
@staticmethod
def remove_old_stats(server_id, last_week):
HelperServerStats.select_database(server_id)
ServerStats.delete().where(ServerStats.created < last_week).execute()
@staticmethod
def get_latest_server_stats(server_id):
HelperServerStats.select_database(server_id)
return (
ServerStats.select()
.where(ServerStats.server_id == server_id)
.order_by(ServerStats.created.desc())
.limit(1)
)
@staticmethod
def get_server_stats_by_id(server_id):
HelperServerStats.select_database(server_id)
stats = (
ServerStats.select()
.where(ServerStats.server_id == server_id)
.order_by(ServerStats.created.desc())
.limit(1)
)
return DatabaseShortcuts.return_rows(stats)[0]
@staticmethod
def server_id_exists(server_id):
HelperServerStats.select_database(server_id)
if not HelperServers.get_server_data_by_id(server_id):
return False
return True
@staticmethod
def sever_crashed(server_id):
HelperServerStats.select_database(server_id)
with database_stats_proxy.atomic():
ServerStats.update(crashed=True).where(
ServerStats.server_id == server_id
).execute()
@staticmethod
def set_download(server_id):
HelperServerStats.select_database(server_id)
with database_stats_proxy.atomic():
ServerStats.update(downloading=True).where(
ServerStats.server_id == server_id
).execute()
@staticmethod
def finish_download(server_id):
HelperServerStats.select_database(server_id)
with database_stats_proxy.atomic():
ServerStats.update(downloading=False).where(
ServerStats.server_id == server_id
).execute()
@staticmethod
def get_download_status(server_id):
HelperServerStats.select_database(server_id)
download_status = (
ServerStats.select().where(ServerStats.server_id == server_id).get()
)
return download_status.downloading
@staticmethod
def server_crash_reset(server_id):
if server_id is None:
return
HelperServerStats.select_database(server_id)
with database_stats_proxy.atomic():
ServerStats.update(crashed=False).where(
ServerStats.server_id == server_id
).execute()
@staticmethod
def is_crashed(server_id):
HelperServerStats.select_database(server_id)
svr = ServerStats.select().where(ServerStats.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):
if server_id is None:
return
HelperServerStats.select_database(server_id)
try:
# Checks if server even exists
ServerStats.select().where(ServerStats.server_id == server_id)
except Exception as ex:
logger.error(f"Database entry not found! {ex}")
with database_stats_proxy.atomic():
ServerStats.update(updating=value).where(
ServerStats.server_id == server_id
).execute()
@staticmethod
def get_update_status(server_id):
HelperServerStats.select_database(server_id)
update_status = (
ServerStats.select().where(ServerStats.server_id == server_id).get()
)
return update_status.updating
@staticmethod
def set_first_run(server_id):
HelperServerStats.select_database(server_id)
# Sets first run to false
try:
# Checks if server even exists
ServerStats.select().where(ServerStats.server_id == server_id)
except Exception as ex:
logger.error(f"Database entry not found! {ex}")
return
with database_stats_proxy.atomic():
ServerStats.update(first_run=False).where(
ServerStats.server_id == server_id
).execute()
@staticmethod
def get_first_run(server_id):
HelperServerStats.select_database(server_id)
first_run = ServerStats.select().where(ServerStats.server_id == server_id).get()
return first_run.first_run
@staticmethod
def get_ttl_without_player(server_id):
HelperServerStats.select_database(server_id)
last_stat = (
ServerStats.select()
.where(ServerStats.server_id == server_id)
.order_by(ServerStats.created.desc())
.first()
)
last_stat_with_player = (
ServerStats.select()
.where(ServerStats.server_id == server_id)
.where(ServerStats.online > 0)
.order_by(ServerStats.created.desc())
.first()
)
return last_stat.created - last_stat_with_player.created
@staticmethod
def can_stop_no_players(server_id, time_limit):
HelperServerStats.select_database(server_id)
can = False
ttl_no_players = HelperServerStats.get_ttl_without_player(server_id)
if (time_limit == -1) or (ttl_no_players > time_limit):
can = True
return can
@staticmethod
def set_waiting_start(server_id, value):
HelperServerStats.select_database(server_id)
try:
# Checks if server even exists
ServerStats.select().where(ServerStats.server_id == server_id)
except Exception as ex:
logger.error(f"Database entry not found! {ex}")
with database_stats_proxy.atomic():
ServerStats.update(waiting_start=value).where(
ServerStats.server_id == server_id
).execute()
@staticmethod
def get_waiting_start(server_id):
HelperServerStats.select_database(server_id)
waiting_start = (
ServerStats.select().where(ServerStats.server_id == server_id).get()
)
return waiting_start.waiting_start

View File

@ -1,26 +1,24 @@
import logging import logging
import datetime import datetime
import typing as t
from peewee import (
CharField,
AutoField,
DateTimeField,
BooleanField,
IntegerField,
)
from playhouse.shortcuts import model_to_dict
from app.classes.shared.helpers import helper from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.shared.main_models import db_helper from app.classes.models.base_model import BaseModel
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__) logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
#************************************************************************************************ # **********************************************************************************
# Servers Class # Servers Class
#************************************************************************************************ # **********************************************************************************
class Servers(Model): class Servers(BaseModel):
server_id = AutoField() server_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
server_uuid = CharField(default="", index=True) server_uuid = CharField(default="", index=True)
@ -42,50 +40,18 @@ class Servers(Model):
class Meta: class Meta:
table_name = "servers" table_name = "servers"
database = database
#************************************************************************************************ # **********************************************************************************
# Servers Stats Class
#************************************************************************************************
class Server_Stats(Model):
stats_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server', index=True)
started = CharField(default="")
running = BooleanField(default=False)
cpu = FloatField(default=0)
mem = FloatField(default=0)
mem_percent = FloatField(default=0)
world_name = CharField(default="")
world_size = CharField(default="")
server_port = IntegerField(default=25565)
int_ping_results = CharField(default="")
online = IntegerField(default=0)
max = IntegerField(default=0)
players = CharField(default="")
desc = CharField(default="Unable to Connect")
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:
table_name = "server_stats"
database = database
#************************************************************************************************
# Servers Class # Servers Class
#************************************************************************************************ # **********************************************************************************
class helper_servers: class HelperServers:
def __init__(self, database):
self.database = database
#************************************************************************************************ # **********************************************************************************
# Generic Servers Methods # Generic Servers Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def create_server( def create_server(
name: str, name: str,
@ -97,23 +63,48 @@ class helper_servers:
server_log_file: str, server_log_file: str,
server_stop: str, server_stop: str,
server_type: str, server_type: str,
server_port=25565): server_port: int = 25565,
return Servers.insert({ server_host: str = "127.0.0.1",
Servers.server_name: name, ) -> int:
Servers.server_uuid: server_uuid, """Create a server in the database
Servers.path: server_dir,
Servers.executable: server_file,
Servers.execution_command: server_command,
Servers.auto_start: False,
Servers.auto_start_delay: 10,
Servers.crash_detection: False,
Servers.log_path: server_log_file,
Servers.server_port: server_port,
Servers.stop_command: server_stop,
Servers.backup_path: backup_path,
Servers.type: server_type
}).execute()
Args:
name: The name of the server
server_uuid: This is the UUID of the server
server_dir: The directory where the server is located
backup_path: The path to the backup folder
server_command: The command to start the server
server_file: The name of the server file
server_log_file: The path to the server log file
server_stop: This is the command to stop the server
server_type: This is the type of server you're creating.
server_port: The port the server will be monitored on, defaults to 25565
server_host: The host the server will be monitored on, defaults to 127.0.0.1
Returns:
int: The new server's id
Raises:
PeeweeException: If the server already exists
"""
return Servers.insert(
{
Servers.server_name: name,
Servers.server_uuid: server_uuid,
Servers.path: server_dir,
Servers.executable: server_file,
Servers.execution_command: server_command,
Servers.auto_start: False,
Servers.auto_start_delay: 10,
Servers.crash_detection: False,
Servers.log_path: server_log_file,
Servers.server_port: server_port,
Servers.server_ip: server_host,
Servers.stop_command: server_stop,
Servers.backup_path: backup_path,
Servers.type: server_type,
}
).execute()
@staticmethod @staticmethod
def get_server_obj(server_id): def get_server_obj(server_id):
@ -128,163 +119,53 @@ class helper_servers:
def update_server(server_obj): def update_server(server_obj):
return server_obj.save() return server_obj.save()
@staticmethod def remove_server(self, server_id):
def remove_server(server_id): with self.database.atomic():
with database.atomic():
Servers.delete().where(Servers.server_id == server_id).execute() Servers.delete().where(Servers.server_id == server_id).execute()
@staticmethod @staticmethod
def get_server_data_by_id(server_id): def get_server_data_by_id(server_id):
query = Servers.select().where(Servers.server_id == server_id).limit(1) query = Servers.select().where(Servers.server_id == server_id).limit(1)
try: try:
return db_helper.return_rows(query)[0] return DatabaseShortcuts.return_rows(query)[0]
except IndexError: except IndexError:
return {} return {}
#************************************************************************************************ @staticmethod
def get_server_columns(
server_id: t.Union[str, int], column_names: t.List[str]
) -> t.List[t.Any]:
columns = [getattr(Servers, column) for column in column_names]
return model_to_dict(
Servers.select(*columns).where(Servers.server_id == server_id).get(),
only=columns,
)
@staticmethod
def get_server_column(server_id: t.Union[str, int], column_name: str) -> t.Any:
column = getattr(Servers, column_name)
return model_to_dict(
Servers.select(column).where(Servers.server_id == server_id).get(),
only=[column],
)[column_name]
# **********************************************************************************
# Servers Methods # Servers Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_all_defined_servers(): def get_all_defined_servers():
query = Servers.select() query = Servers.select()
return db_helper.return_rows(query) return DatabaseShortcuts.return_rows(query)
@staticmethod @staticmethod
def get_all_servers_stats(): def get_all_server_ids() -> t.List[int]:
servers = servers_helper.get_all_defined_servers() return [server.server_id for server in Servers.select(Servers.server_id)]
server_data = []
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 @staticmethod
def get_server_friendly_name(server_id): def get_server_friendly_name(server_id):
server_data = servers_helper.get_server_data_by_id(server_id) server_data = HelperServers.get_server_data_by_id(server_id)
friendly_name = f"{server_data.get('server_name', None)} with ID: {server_data.get('server_id', 0)}" friendly_name = (
f"{server_data.get('server_name', None)} "
f"with ID: {server_data.get('server_id', 0)}"
)
return friendly_name return friendly_name
#************************************************************************************************
# Servers_Stats Methods
#************************************************************************************************
@staticmethod
def get_latest_server_stats(server_id):
return Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).limit(1)
@staticmethod
def get_server_stats_by_id(server_id):
stats = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).limit(1)
return db_helper.return_rows(stats)[0]
@staticmethod
def server_id_exists(server_id):
if not servers_helper.get_server_data_by_id(server_id):
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:
#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}")
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())
return last_stat.created - last_stat_with_player.created
@staticmethod
def can_stop_no_players(server_id, time_limit):
can = False
ttl_no_players = servers_helper.get_TTL_without_player(server_id)
if (time_limit == -1) or (ttl_no_players > time_limit):
can = True
return can
@staticmethod
def set_waiting_start(server_id, value):
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}")
with database.atomic():
Server_Stats.update(waiting_start=value).where(Server_Stats.server_id == server_id).execute()
@staticmethod
def get_waiting_start(server_id):
waiting_start = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
return waiting_start.waiting_start
servers_helper = helper_servers()

View File

@ -1,28 +1,29 @@
import logging import logging
import datetime import datetime
from typing import Optional, Union import typing as t
from app.classes.models.roles import Roles, roles_helper from peewee import (
from app.classes.shared.helpers import helper ForeignKeyField,
CharField,
AutoField,
DateTimeField,
BooleanField,
CompositeKey,
DoesNotExist,
JOIN,
)
from playhouse.shortcuts import model_to_dict
try: from app.classes.shared.helpers import Helpers
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, AutoField, DateTimeField, BooleanField, CompositeKey, DoesNotExist, JOIN from app.classes.models.base_model import BaseModel
from playhouse.shortcuts import model_to_dict from app.classes.models.roles import Roles, HelperRoles
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
#************************************************************************************************ # **********************************************************************************
# Users Class # Users Class
#************************************************************************************************ # **********************************************************************************
class Users(Model): class Users(BaseModel):
user_id = AutoField() user_id = AutoField()
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
last_login = DateTimeField(default=datetime.datetime.now) last_login = DateTimeField(default=datetime.datetime.now)
@ -34,48 +35,60 @@ class Users(Model):
enabled = BooleanField(default=True) enabled = BooleanField(default=True)
superuser = BooleanField(default=False) superuser = BooleanField(default=False)
lang = CharField(default="en_EN") lang = CharField(default="en_EN")
support_logs = CharField(default = '') support_logs = CharField(default="")
valid_tokens_from = DateTimeField(default=datetime.datetime.now) valid_tokens_from = DateTimeField(default=datetime.datetime.now)
server_order = CharField(default="") server_order = CharField(default="")
preparing = BooleanField(default=False)
hints = BooleanField(default=True)
class Meta: class Meta:
table_name = "users" table_name = "users"
database = database
# ************************************************************************************************ PUBLIC_USER_ATTRS: t.Final = [
"user_id",
"created",
"username",
"enabled",
"superuser",
"lang", # maybe remove?
]
# **********************************************************************************
# API Keys Class # API Keys Class
# ************************************************************************************************ # **********************************************************************************
class ApiKeys(Model): class ApiKeys(BaseModel):
token_id = AutoField() token_id = AutoField()
name = CharField(default='', unique=True, index=True) name = CharField(default="", unique=True, index=True)
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
user_id = ForeignKeyField(Users, backref='api_token', index=True) user_id = ForeignKeyField(Users, backref="api_token", index=True)
server_permissions = CharField(default='00000000') server_permissions = CharField(default="00000000")
crafty_permissions = CharField(default='000') crafty_permissions = CharField(default="000")
superuser = BooleanField(default=False) superuser = BooleanField(default=False)
class Meta: class Meta:
table_name = 'api_keys' table_name = "api_keys"
database = database
#************************************************************************************************ # **********************************************************************************
# User Roles Class # User Roles Class
#************************************************************************************************ # **********************************************************************************
class User_Roles(Model): class UserRoles(BaseModel):
user_id = ForeignKeyField(Users, backref='user_role') user_id = ForeignKeyField(Users, backref="user_role")
role_id = ForeignKeyField(Roles, backref='user_role') role_id = ForeignKeyField(Roles, backref="user_role")
class Meta: class Meta:
table_name = 'user_roles' table_name = "user_roles"
primary_key = CompositeKey('user_id', 'role_id') primary_key = CompositeKey("user_id", "role_id")
database = database
#************************************************************************************************
# **********************************************************************************
# Users Helpers # Users Helpers
#************************************************************************************************ # **********************************************************************************
class helper_users: class HelperUsers:
def __init__(self, database, helper):
self.database = database
self.helper = helper
@staticmethod @staticmethod
def get_by_id(user_id): def get_by_id(user_id):
@ -86,6 +99,15 @@ class helper_users:
query = Users.select().where(Users.username != "system") query = Users.select().where(Users.username != "system")
return query return query
@staticmethod
def get_all_user_ids() -> t.List[int]:
return [
user.user_id
for user in Users.select(Users.user_id)
.where(Users.username != "system")
.execute()
]
@staticmethod @staticmethod
def get_user_lang_by_id(user_id): def get_user_lang_by_id(user_id):
return Users.get(Users.user_id == user_id).lang return Users.get(Users.user_id == user_id).lang
@ -106,30 +128,48 @@ class helper_users:
def get_user(user_id): def get_user(user_id):
if user_id == 0: if user_id == 0:
return { return {
'user_id': 0, "user_id": 0,
'created': '10/24/2019, 11:34:00', "created": "10/24/2019, 11:34:00",
'last_login': '10/24/2019, 11:34:00', "last_login": "10/24/2019, 11:34:00",
'last_update': '10/24/2019, 11:34:00', "last_update": "10/24/2019, 11:34:00",
'last_ip': "127.27.23.89", "last_ip": "127.27.23.89",
'username': "SYSTEM", "username": "SYSTEM",
'password': None, "password": None,
'email': "default@example.com", "email": "default@example.com",
'enabled': True, "enabled": True,
'superuser': True, "superuser": True,
'roles': [], "roles": [],
'servers': [], "servers": [],
'support_logs': '', "support_logs": "",
} }
user = model_to_dict(Users.get(Users.user_id == user_id)) user = model_to_dict(Users.get(Users.user_id == user_id))
if user: if user:
# I know it should apply it without setting it but I'm just making sure # I know it should apply it without setting it but I'm just making sure
user = users_helper.add_user_roles(user) user = HelperUsers.add_user_roles(user)
return user return user
else: else:
#logger.debug("user: ({}) {}".format(user_id, {})) # logger.debug("user: ({}) {}".format(user_id, {}))
return {} return {}
@staticmethod
def get_user_columns(
user_id: t.Union[str, int], column_names: t.List[str]
) -> t.List[t.Any]:
columns = [getattr(Users, column) for column in column_names]
return model_to_dict(
Users.select(*columns).where(Users.user_id == user_id).get(),
only=columns,
)
@staticmethod
def get_user_column(user_id: t.Union[str, int], column_name: str) -> t.Any:
column = getattr(Users, column_name)
return model_to_dict(
Users.select(column).where(Users.user_id == user_id).get(),
only=[column],
)[column_name]
@staticmethod @staticmethod
def check_system_user(user_id): def check_system_user(user_id):
try: try:
@ -142,23 +182,51 @@ class helper_users:
@staticmethod @staticmethod
def get_user_model(user_id: str) -> Users: def get_user_model(user_id: str) -> Users:
user = Users.get(Users.user_id == user_id) user = Users.get(Users.user_id == user_id)
user = users_helper.add_user_roles(user) user = HelperUsers.add_user_roles(user)
return user return user
@staticmethod def add_user(
def add_user(username: str, password: Optional[str] = None, email: Optional[str] = None, enabled: bool = True, superuser: bool = False) -> str: self,
username: str,
password: str = None,
email: t.Optional[str] = None,
enabled: bool = True,
superuser: bool = False,
) -> str:
if password is not None: if password is not None:
pw_enc = helper.encode_pass(password) pw_enc = self.helper.encode_pass(password)
else: else:
pw_enc = None pw_enc = None
user_id = Users.insert({ user_id = Users.insert(
Users.username: username.lower(), {
Users.password: pw_enc, Users.username: username.lower(),
Users.email: email, Users.password: pw_enc,
Users.enabled: enabled, Users.email: email,
Users.superuser: superuser, Users.enabled: enabled,
Users.created: helper.get_time_as_string() Users.superuser: superuser,
}).execute() Users.created: Helpers.get_time_as_string(),
}
).execute()
return user_id
@staticmethod
def add_rawpass_user(
username: str,
password: str = None,
email: t.Optional[str] = None,
enabled: bool = True,
superuser: bool = False,
) -> str:
user_id = Users.insert(
{
Users.username: username.lower(),
Users.password: password,
Users.email: email,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: Helpers.get_time_as_string(),
}
).execute()
return user_id return user_id
@staticmethod @staticmethod
@ -170,7 +238,9 @@ class helper_users:
@staticmethod @staticmethod
def update_server_order(user_id, user_server_order): def update_server_order(user_id, user_server_order):
Users.update(server_order = user_server_order).where(Users.user_id == user_id).execute() Users.update(server_order=user_server_order).where(
Users.user_id == user_id
).execute()
@staticmethod @staticmethod
def get_server_order(user_id): def get_server_order(user_id):
@ -178,104 +248,128 @@ class helper_users:
@staticmethod @staticmethod
def get_super_user_list(): def get_super_user_list():
final_users = [] final_users: t.List[int] = []
# pylint: disable=singleton-comparison super_users = Users.select().where(
super_users = Users.select().where(Users.superuser == True) Users.superuser == True # pylint: disable=singleton-comparison
)
for suser in super_users: for suser in super_users:
if suser.user_id not in final_users: if suser.user_id not in final_users:
final_users.append(suser.user_id) final_users.append(suser.user_id)
return final_users return final_users
@staticmethod def remove_user(self, user_id):
def remove_user(user_id): with self.database.atomic():
with database.atomic(): UserRoles.delete().where(UserRoles.user_id == user_id).execute()
User_Roles.delete().where(User_Roles.user_id == user_id).execute() return Users.delete().where(Users.user_id == user_id).execute()
user = Users.get(Users.user_id == user_id)
return user.delete_instance()
@staticmethod @staticmethod
def set_support_path(user_id, support_path): def set_support_path(user_id, support_path):
Users.update(support_logs = support_path).where(Users.user_id == user_id).execute() Users.update(support_logs=support_path).where(
Users.user_id == user_id
).execute()
@staticmethod
def set_prepare(user_id):
Users.update(preparing=True).where(Users.user_id == user_id).execute()
@staticmethod
def stop_prepare(user_id):
Users.update(preparing=False).where(Users.user_id == user_id).execute()
@staticmethod
def clear_support_status():
Users.update(preparing=False).where(
Users.preparing == True # pylint: disable=singleton-comparison
).execute()
@staticmethod @staticmethod
def user_id_exists(user_id): def user_id_exists(user_id):
if not users_helper.get_user(user_id): if not HelperUsers.get_user(user_id):
return False return False
return True return True
#************************************************************************************************ # **********************************************************************************
# User_Roles Methods # User_Roles Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def get_or_create(user_id, role_id): def get_or_create(user_id, role_id):
return User_Roles.get_or_create(user_id=user_id, role_id=role_id) return UserRoles.get_or_create(user_id=user_id, role_id=role_id)
@staticmethod @staticmethod
def get_user_roles_id(user_id): def get_user_roles_id(user_id):
roles_list = [] roles_list = []
roles = User_Roles.select().where(User_Roles.user_id == user_id) roles = UserRoles.select().where(UserRoles.user_id == user_id)
for r in roles: for r in roles:
roles_list.append(roles_helper.get_role(r.role_id)['role_id']) roles_list.append(HelperRoles.get_role(r.role_id)["role_id"])
return roles_list return roles_list
@staticmethod @staticmethod
def get_user_roles_names(user_id): def get_user_roles_names(user_id):
roles_list = [] roles = UserRoles.select(UserRoles.role_id).where(UserRoles.user_id == user_id)
roles = User_Roles.select().where(User_Roles.user_id == user_id) return [
for r in roles: HelperRoles.get_role_column(role.role_id, "role_name") for role in roles
roles_list.append(roles_helper.get_role(r.role_id)['role_name']) ]
return roles_list
@staticmethod @staticmethod
def add_role_to_user(user_id, role_id): def add_role_to_user(user_id, role_id):
User_Roles.insert({ UserRoles.insert(
User_Roles.user_id: user_id, {UserRoles.user_id: user_id, UserRoles.role_id: role_id}
User_Roles.role_id: role_id ).execute()
}).execute()
@staticmethod @staticmethod
def add_user_roles(user: Union[dict, Users]): def add_user_roles(user: t.Union[dict, Users]):
if isinstance(user, dict): if isinstance(user, dict):
user_id = user['user_id'] user_id = user["user_id"]
else: else:
user_id = user.user_id user_id = user.user_id
# I just copied this code from get_user, it had those TODOs & comments made by mac - Lukas # I just copied this code from get_user,
# it had those TODOs & comments made by mac - Lukas
roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id) roles_query = (
UserRoles.select()
.join(Roles, JOIN.INNER)
.where(UserRoles.user_id == user_id)
)
# TODO: this query needs to be narrower # TODO: this query needs to be narrower
roles = set() roles = set()
for r in roles_query: for r in roles_query:
roles.add(r.role_id.role_id) roles.add(r.role_id.role_id)
if isinstance(user, dict): if isinstance(user, dict):
user['roles'] = roles user["roles"] = roles
else: else:
user.roles = roles user.roles = roles
#logger.debug("user: ({}) {}".format(user_id, user)) # logger.debug("user: ({}) {}".format(user_id, user))
return user return user
@staticmethod @staticmethod
def user_role_query(user_id): def user_role_query(user_id):
user_query = User_Roles.select().where(User_Roles.user_id == user_id) user_query = UserRoles.select().where(UserRoles.user_id == user_id)
query = Roles.select().where(Roles.role_id == -1) query = Roles.select().where(Roles.role_id == -1)
for u in user_query: for user in user_query:
query = query + Roles.select().where(Roles.role_id == u.role_id) query = query + Roles.select().where(Roles.role_id == user.role_id)
return query return query
@staticmethod @staticmethod
def delete_user_roles(user_id, removed_roles): def delete_user_roles(user_id, removed_roles):
User_Roles.delete().where(User_Roles.user_id == user_id).where(User_Roles.role_id.in_(removed_roles)).execute() UserRoles.delete().where(UserRoles.user_id == user_id).where(
UserRoles.role_id.in_(removed_roles)
).execute()
@staticmethod @staticmethod
def remove_roles_from_role_id(role_id): def remove_roles_from_role_id(role_id):
User_Roles.delete().where(User_Roles.role_id == role_id).execute() UserRoles.delete().where(UserRoles.role_id == role_id).execute()
# ************************************************************************************************ @staticmethod
# ApiKeys Methods def get_users_from_role(role_id):
# ************************************************************************************************ UserRoles.select().where(UserRoles.role_id == role_id).execute()
# **********************************************************************************
# ApiKeys Methods
# **********************************************************************************
@staticmethod @staticmethod
def get_user_api_keys(user_id: str): def get_user_api_keys(user_id: str):
@ -287,18 +381,29 @@ class helper_users:
@staticmethod @staticmethod
def add_user_api_key( def add_user_api_key(
name: str, name: str,
user_id: str, user_id: str,
superuser: bool = False, superuser: bool = False,
server_permissions_mask: Optional[str] = None, server_permissions_mask: t.Optional[str] = None,
crafty_permissions_mask: Optional[str] = None): crafty_permissions_mask: t.Optional[str] = None,
return ApiKeys.insert({ ):
ApiKeys.name: name, return ApiKeys.insert(
ApiKeys.user_id: user_id, {
**({ApiKeys.server_permissions: server_permissions_mask} if server_permissions_mask is not None else {}), ApiKeys.name: name,
**({ApiKeys.crafty_permissions: crafty_permissions_mask} if crafty_permissions_mask is not None else {}), ApiKeys.user_id: user_id,
ApiKeys.superuser: superuser **(
}).execute() {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 @staticmethod
def delete_user_api_keys(user_id: str): def delete_user_api_keys(user_id: str):
@ -307,7 +412,3 @@ class helper_users:
@staticmethod @staticmethod
def delete_user_api_key(key_id: str): def delete_user_api_key(key_id: str):
ApiKeys.delete().where(ApiKeys.token_id == key_id).execute() ApiKeys.delete().where(ApiKeys.token_id == key_id).execute()
users_helper = helper_users()

View File

@ -1,79 +1,81 @@
import logging import logging
import time import time
from typing import Optional, Dict, Any, Tuple from typing import Optional, Dict, Any, Tuple
import jwt
from jwt import PyJWTError
from app.classes.models.users import users_helper, ApiKeys from app.classes.models.users import HelperUsers, 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__) logger = logging.getLogger(__name__)
class Authentication: class Authentication:
def __init__(self): def __init__(self, helper):
self.helper = helper
self.secret = "my secret" self.secret = "my secret"
self.secret = helper.get_setting('apikey_secret', None) self.secret = self.helper.get_setting("apikey_secret", None)
if self.secret is None or self.secret == 'random': if self.secret is None or self.secret == "random":
self.secret = helper.random_string_generator(64) self.secret = self.helper.random_string_generator(64)
self.helper.set_setting("apikey_secret", self.secret)
@staticmethod def generate(self, user_id, extra=None):
def generate(user_id, extra=None):
if extra is None: if extra is None:
extra = {} extra = {}
return jwt.encode( jwt_encoded = jwt.encode(
{ {"user_id": user_id, "iat": int(time.time()), **extra},
'user_id': user_id, self.secret,
'iat': int(time.time()), algorithm="HS256",
**extra
},
authentication.secret,
algorithm="HS256"
) )
return jwt_encoded
@staticmethod def read(self, token):
def read(token): return jwt.decode(token, self.secret, algorithms=["HS256"])
return jwt.decode(token, authentication.secret, algorithms=["HS256"])
@staticmethod def check_no_iat(self, token) -> Optional[Dict[str, Any]]:
def check_no_iat(token) -> Optional[Dict[str, Any]]:
try: try:
return jwt.decode(token, authentication.secret, algorithms=["HS256"]) return jwt.decode(str(token), self.secret, algorithms=["HS256"])
except PyJWTError as error: except PyJWTError as error:
logger.debug("Error while checking JWT token: ", exc_info=error) logger.debug("Error while checking JWT token: ", exc_info=error)
return None return None
@staticmethod def check(
def check(token) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]: self,
token,
) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
try: try:
data = jwt.decode(token, authentication.secret, algorithms=["HS256"]) data = jwt.decode(str(token), self.secret, algorithms=["HS256"])
except PyJWTError as error: except PyJWTError as error:
logger.debug("Error while checking JWT token: ", exc_info=error) logger.debug("Error while checking JWT token: ", exc_info=error)
return None return None
iat: int = data['iat'] iat: int = data["iat"]
key: Optional[ApiKeys] = None key: Optional[ApiKeys] = None
if 'token_id' in data: if "token_id" in data:
key_id = data['token_id'] key_id = data["token_id"]
key = users_helper.get_user_api_key(key_id) key = HelperUsers.get_user_api_key(key_id)
if key is None: if key is None:
return None return None
user_id: str = data['user_id'] user_id: str = data["user_id"]
user = users_helper.get_user(user_id) user = HelperUsers.get_user(user_id)
# TODO: Have a cache or something so we don't constantly have to query the database # TODO: Have a cache or something so we don't constantly
if int(user.get('valid_tokens_from').timestamp()) < iat: # have to query the database
if int(user.get("valid_tokens_from").timestamp()) < iat:
# Success! # Success!
return key, data, user return key, data, user
else: else:
return None return None
@staticmethod def check_err(
def check_bool(token) -> bool: self,
return authentication.check(token) is not None token,
) -> Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]:
# Without this function there would be runtime exceptions like the following:
# "None" object is not iterable
output = self.check(token)
if output is None:
raise Exception("Invalid token")
return output
authentication = Authentication() def check_bool(self, token) -> bool:
return self.check(token) is not None

View File

@ -3,75 +3,117 @@ import cmd
import time import time
import threading import threading
import logging import logging
import getpass
from app.classes.shared.console import Console
from app.classes.shared.console import console from app.classes.shared.import3 import Import3
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class MainPrompt(cmd.Cmd):
def __init__(self, tasks_manager, migration_manager): class MainPrompt(cmd.Cmd):
def __init__(self, helper, tasks_manager, migration_manager, main_controller):
super().__init__() super().__init__()
self.helper = helper
self.tasks_manager = tasks_manager self.tasks_manager = tasks_manager
self.migration_manager = migration_manager self.migration_manager = migration_manager
self.controller = main_controller
# overrides the default Prompt # overrides the default Prompt
prompt = f"Crafty Controller v{helper.get_version_string()} > " self.prompt = f"Crafty Controller v{self.helper.get_version_string()} > "
# see MR !233 for pylint exemptino reason
@staticmethod @staticmethod
def emptyline(): def emptyline(): # pylint: disable=arguments-differ
pass pass
#pylint: disable=unused-argument def do_exit(self, _line):
def do_exit(self, line):
self.tasks_manager._main_graceful_exit() self.tasks_manager._main_graceful_exit()
self.universal_exit() self.universal_exit()
def do_migrations(self, line): def do_migrations(self, line):
if line == 'up': if line == "up":
self.migration_manager.up() self.migration_manager.up()
elif line == 'down': elif line == "down":
self.migration_manager.down() self.migration_manager.down()
elif line == 'done': elif line == "done":
console.info(self.migration_manager.done) Console.info(self.migration_manager.done)
elif line == 'todo': elif line == "todo":
console.info(self.migration_manager.todo) Console.info(self.migration_manager.todo)
elif line == 'diff': elif line == "diff":
console.info(self.migration_manager.diff) Console.info(self.migration_manager.diff)
elif line == 'info': elif line == "info":
console.info(f'Done: {self.migration_manager.done}') Console.info(f"Done: {self.migration_manager.done}")
console.info(f'FS: {self.migration_manager.todo}') Console.info(f"FS: {self.migration_manager.todo}")
console.info(f'Todo: {self.migration_manager.diff}') Console.info(f"Todo: {self.migration_manager.diff}")
elif line.startswith('add '): elif line.startswith("add "):
migration_name = line[len('add '):] migration_name = line[len("add ") :]
self.migration_manager.create(migration_name, False) self.migration_manager.create(migration_name, False)
else: else:
console.info('Unknown migration command') Console.info("Unknown migration command")
def do_set_passwd(self, line):
try:
username = str(line).lower()
# If no user is found it returns None
user_id = self.controller.users.get_id_by_name(username)
if not username:
Console.error("You must enter a username. Ex: `set_passwd admin'")
return False
if not user_id:
Console.error(f"No user found by the name of {username}")
return False
except:
Console.error(f"User: {line} Not Found")
return False
new_pass = getpass.getpass(prompt=f"NEW password for: {username} > ")
new_pass_conf = getpass.getpass(prompt="Re-enter your password: > ")
if new_pass != new_pass_conf:
Console.error("Passwords do not match. Please try again.")
return False
if len(new_pass) > 512:
Console.warning("Passwords must be greater than 6char long and under 512")
return False
if len(new_pass) < 6:
Console.warning("Passwords must be greater than 6char long and under 512")
return False
self.controller.users.update_user(user_id, {"password": new_pass})
@staticmethod @staticmethod
def do_threads(_line): def do_threads(_line):
for thread in threading.enumerate(): for thread in threading.enumerate():
if sys.version_info >= (3, 8): if sys.version_info >= (3, 8):
print(f'Name: {thread.name} Identifier: {thread.ident} TID/PID: {thread.native_id}') print(
f"Name: {thread.name} Identifier: "
f"{thread.ident} TID/PID: {thread.native_id}"
)
else: else:
print(f'Name: {thread.name} Identifier: {thread.ident}') print(f"Name: {thread.name} Identifier: {thread.ident}")
def do_import3(self, _line):
Import3.start_import()
def universal_exit(self): def universal_exit(self):
logger.info("Stopping all server daemons / threads") logger.info("Stopping all server daemons / threads")
console.info("Stopping all server daemons / threads - This may take a few seconds") Console.info(
websocket_helper.disconnect_all() "Stopping all server daemons / threads - This may take a few seconds"
console.info('Waiting for main thread to stop') )
self.helper.websocket_helper.disconnect_all()
Console.info("Waiting for main thread to stop")
while True: while True:
if self.tasks_manager.get_main_thread_run_status(): if self.tasks_manager.get_main_thread_run_status():
sys.exit(0) sys.exit(0)
time.sleep(1) time.sleep(1)
@staticmethod def help_exit(self):
def help_exit(): Console.help("Stops the server if running, Exits the program")
console.help("Stops the server if running, Exits the program")
@staticmethod def help_migrations(self):
def help_migrations(): Console.help("Only for advanced users. Use with caution")
console.help("Only for advanced users. Use with caution")
def help_import3(self):
Console.help("Import users and servers from Crafty 3")

View File

@ -12,61 +12,72 @@ except ModuleNotFoundError as ex:
logger.critical(f"Import Error: Unable to load {ex.name} module", exc_info=True) logger.critical(f"Import Error: Unable to load {ex.name} module", exc_info=True)
print(f"Import Error: Unable to load {ex.name} module") print(f"Import Error: Unable to load {ex.name} module")
from app.classes.shared.installer import installer from app.classes.shared.installer import installer
installer.do_install()
class Console:
installer.do_install()
class Console:
def __init__(self): def __init__(self):
if 'colorama' in sys.modules: if "colorama" in sys.modules:
init() init()
@staticmethod @staticmethod
def do_print(message, color): def do_print(message, color):
if 'termcolor' in sys.modules or 'colorama' in sys.modules: if "termcolor" in sys.modules or "colorama" in sys.modules:
print(colored(message, color)) print(colored(message, color))
else: else:
print(message) print(message)
def magenta(self, message): @staticmethod
self.do_print(message, "magenta") def magenta(message):
Console.do_print(message, "magenta")
def cyan(self, message): @staticmethod
self.do_print(message, "cyan") def cyan(message):
Console.do_print(message, "cyan")
def yellow(self, message): @staticmethod
self.do_print(message, "yellow") def yellow(message):
Console.do_print(message, "yellow")
def red(self, message): @staticmethod
self.do_print(message, "red") def red(message):
Console.do_print(message, "red")
def green(self, message): @staticmethod
self.do_print(message, "green") def green(message):
Console.do_print(message, "green")
def white(self, message): @staticmethod
self.do_print(message, "white") def white(message):
Console.do_print(message, "white")
def debug(self, message): @staticmethod
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") def debug(message):
self.magenta(f"[+] Crafty: {dt} - DEBUG:\t{message}") date_time = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
Console.magenta(f"[+] Crafty: {date_time} - DEBUG:\t{message}")
def info(self, message): @staticmethod
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") def info(message):
self.white(f"[+] Crafty: {dt} - INFO:\t{message}") date_time = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
Console.white(f"[+] Crafty: {date_time} - INFO:\t{message}")
def warning(self, message): @staticmethod
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") def warning(message):
self.cyan(f"[+] Crafty: {dt} - WARNING:\t{message}") date_time = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
Console.cyan(f"[+] Crafty: {date_time} - WARNING:\t{message}")
def error(self, message): @staticmethod
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") def error(message):
self.yellow(f"[+] Crafty: {dt} - ERROR:\t{message}") date_time = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
Console.yellow(f"[+] Crafty: {date_time} - ERROR:\t{message}")
def critical(self, message): @staticmethod
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") def critical(message):
self.red(f"[+] Crafty: {dt} - CRITICAL:\t{message}") date_time = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
Console.red(f"[+] Crafty: {date_time} - CRITICAL:\t{message}")
def help(self, message): @staticmethod
dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") def help(message):
self.green(f"[+] Crafty: {dt} - HELP:\t{message}") date_time = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
Console.green(f"[+] Crafty: {date_time} - HELP:\t{message}")
console = Console()

View File

@ -1,8 +1,10 @@
class CraftyException(Exception): class CraftyException(Exception):
pass pass
class DatabaseException(CraftyException): class DatabaseException(CraftyException):
pass pass
class SchemaError(DatabaseException): class SchemaError(DatabaseException):
pass pass

View File

@ -6,19 +6,17 @@ from zipfile import ZipFile, ZIP_DEFLATED
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class FileHelpers:
allowed_quotes = [
"\"",
"'",
"`"
]
def del_dirs(self, path): class FileHelpers:
allowed_quotes = ['"', "'", "`"]
@staticmethod
def del_dirs(path):
path = pathlib.Path(path) path = pathlib.Path(path)
for sub in path.iterdir(): for sub in path.iterdir():
if sub.is_dir(): if sub.is_dir():
# Delete folder if it is a folder # Delete folder if it is a folder
self.del_dirs(sub) FileHelpers.del_dirs(sub)
else: else:
# Delete file if it is a file: # Delete file if it is a file:
sub.unlink() sub.unlink()
@ -32,7 +30,7 @@ class FileHelpers:
path = pathlib.Path(path) path = pathlib.Path(path)
try: try:
logger.debug(f"Deleting file: {path}") logger.debug(f"Deleting file: {path}")
#Remove the file # Remove the file
os.remove(path) os.remove(path)
return True return True
except FileNotFoundError: except FileNotFoundError:
@ -48,54 +46,70 @@ class FileHelpers:
def copy_file(src_path, dest_path): def copy_file(src_path, dest_path):
shutil.copy(src_path, dest_path) shutil.copy(src_path, dest_path)
def move_dir(self, src_path, dest_path): @staticmethod
self.copy_dir(src_path, dest_path) def move_dir(src_path, dest_path):
self.del_dirs(src_path) FileHelpers.copy_dir(src_path, dest_path)
FileHelpers.del_dirs(src_path)
def move_file(self, src_path, dest_path): @staticmethod
self.copy_file(src_path, dest_path) def move_file(src_path, dest_path):
self.del_file(src_path) FileHelpers.copy_file(src_path, dest_path)
FileHelpers.del_file(src_path)
@staticmethod @staticmethod
def make_archive(path_to_destination, path_to_zip): def make_archive(path_to_destination, path_to_zip):
# create a ZipFile object # create a ZipFile object
path_to_destination += '.zip' path_to_destination += ".zip"
with ZipFile(path_to_destination, 'w') as z: with ZipFile(path_to_destination, "w") as zip_file:
for root, _dirs, files in os.walk(path_to_zip, topdown=True): for root, _dirs, files in os.walk(path_to_zip, topdown=True):
ziproot = path_to_zip ziproot = path_to_zip
for file in files: for file in files:
try: try:
logger.info(f"backing up: {os.path.join(root, file)}") logger.info(f"backing up: {os.path.join(root, file)}")
if os.name == "nt": if os.name == "nt":
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, ""), file)) zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, ""), file),
)
else: else:
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, "/"), file)) zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, "/"), file),
)
except Exception as e: except Exception as e:
logger.warning(f"Error backing up: {os.path.join(root, file)}! - Error was: {e}") logger.warning(
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
return True return True
@staticmethod @staticmethod
def make_compressed_archive(path_to_destination, path_to_zip): def make_compressed_archive(path_to_destination, path_to_zip):
# create a ZipFile object # create a ZipFile object
path_to_destination += '.zip' path_to_destination += ".zip"
with ZipFile(path_to_destination, 'w', ZIP_DEFLATED) as z: with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file:
for root, _dirs, files in os.walk(path_to_zip, topdown=True): for root, _dirs, files in os.walk(path_to_zip, topdown=True):
ziproot = path_to_zip ziproot = path_to_zip
for file in files: for file in files:
try: try:
logger.info(f"backing up: {os.path.join(root, file)}") logger.info(f"backing up: {os.path.join(root, file)}")
if os.name == "nt": if os.name == "nt":
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, ""), file)) zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, ""), file),
)
else: else:
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, "/"), file)) zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, "/"), file),
)
except Exception as e: except Exception as e:
logger.warning(f"Error backing up: {os.path.join(root, file)}! - Error was: {e}") logger.warning(
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
return True return True
file_helper = FileHelpers()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
import json
import os
import logging
from app.classes.controllers.users_controller import HelperUsers
from app.classes.shared.console import Console
logger = logging.getLogger(__name__)
class Import3:
def __init__(self, helper, controller):
self.helper = helper
self.controller = controller
def start_import(self):
folder = os.path.normpath(
input(
"Please input the path to the migrations folder "
"in your installation of Crafty 3: "
)
)
if not os.path.exists(folder):
Console.info(
"Crafty cannot find the path you entered. "
"Does Crafty's user have permission to access it?"
)
Console.info("Please run the import3 command again and enter a valid path.")
else:
with open(os.path.join(folder, "users.json"), encoding="utf-8") as f:
user_json = json.loads(f.read())
with open(os.path.join(folder, "mc_settings.json"), encoding="utf-8") as f:
servers_json = json.loads(f.read())
self.import_users(user_json)
self.import_servers(servers_json, self.controller)
def import_users(self, json_data):
# If there is only one user to import json needs to call the data differently
if isinstance(json_data, list):
for user in json_data:
HelperUsers.add_rawpass_user(user["username"], user["password"])
Console.info(f"Imported user {user['username']} from Crafty 3")
logger.info(f"Imported user {user['username']} from Crafty 3")
else:
Console.info(
"There is only one user detected. "
"Cannot create duplicate Admin account."
)
logger.info(
"There is only one user detected. "
"Cannot create duplicate Admin account."
)
def import_servers(self, json_data, controller):
# If there is only one server to import json needs to call the data differently
if isinstance(json_data, list):
for server in json_data:
new_server_id = controller.import_jar_server(
server_name=server["server_name"],
server_path=server["server_path"],
server_jar=server["server_jar"],
min_mem=(int(server["memory_min"]) / 1000),
max_mem=(int(server["memory_max"]) / 1000),
port=server["server_port"],
)
Console.info(
f"Imported server {server['server_name']}[{server['id']}] "
f"from Crafty 3 to new server id {new_server_id}"
)
logger.info(
f"Imported server {server['server_name']}[{server['id']}] "
f"from Crafty 3 to new server id {new_server_id}"
)
else:
new_server_id = controller.import_jar_server(
server_name=json_data["server_name"],
server_path=json_data["server_path"],
server_jar=json_data["server_jar"],
min_mem=(int(json_data["memory_min"]) / 1000),
max_mem=(int(json_data["memory_max"]) / 1000),
port=json_data["server_port"],
)
Console.info(
f"Imported server {json_data['server_name']}[{json_data['id']}] "
f"from Crafty 3 to new server id {new_server_id}"
)
logger.info(
f"Imported server {json_data['server_name']}[{json_data['id']}] "
f"from Crafty 3 to new server id {new_server_id}"
)

View File

@ -1,12 +1,13 @@
import sys import sys
import subprocess import subprocess
class install:
class Install:
@staticmethod @staticmethod
def is_venv(): def is_venv():
return (hasattr(sys, 'real_prefix') or return hasattr(sys, "real_prefix") or (
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)) hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
)
def do_install(self): def do_install(self):
@ -16,8 +17,11 @@ class install:
sys.exit(1) sys.exit(1)
# do our pip install # do our pip install
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", 'requirements.txt']) subprocess.check_call(
[sys.executable, "-m", "pip", "install", "-r", "requirements.txt"]
)
print("Crafty has installed it's dependencies, please restart Crafty") print("Crafty has installed it's dependencies, please restart Crafty")
sys.exit(0) sys.exit(0)
installer = install()
installer = Install()

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +1,46 @@
import logging import logging
from playhouse.shortcuts import model_to_dict
from app.classes.models.users import Users, users_helper from app.classes.shared.helpers import Helpers # pylint: disable=unused-import
from app.classes.shared.helpers import helper from app.classes.shared.console import Console
from app.classes.shared.console import console
# 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__) logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
class db_builder:
@staticmethod class DatabaseBuilder:
def default_settings(): def __init__(self, database, helper, users_helper):
self.database = database
self.helper = helper
self.users_helper = users_helper
def default_settings(self):
logger.info("Fresh Install Detected - Creating Default Settings") logger.info("Fresh Install Detected - Creating Default Settings")
console.info("Fresh Install Detected - Creating Default Settings") Console.info("Fresh Install Detected - Creating Default Settings")
default_data = helper.find_default_password() default_data = self.helper.find_default_password()
username = default_data.get("username", 'admin') username = default_data.get("username", "admin")
password = default_data.get("password", 'crafty') password = default_data.get("password", "crafty")
users_helper.add_user(username=username, password=password, email="default@example.com", superuser=True) self.users_helper.add_user(
username=username,
password=password,
email="default@example.com",
superuser=True,
)
@staticmethod def is_fresh_install(self):
def is_fresh_install():
try: try:
user = users_helper.get_by_id(1) user = self.users_helper.get_by_id(1)
if user: if user:
return False return False
except: except:
return True return True
class db_shortcuts:
#************************************************************************************************ class DatabaseShortcuts:
# **********************************************************************************
# Generic Databse Methods # Generic Databse Methods
#************************************************************************************************ # **********************************************************************************
@staticmethod @staticmethod
def return_rows(query): def return_rows(query):
rows = [] rows = []
@ -67,10 +58,3 @@ class db_shortcuts:
def return_db_rows(model): def return_db_rows(model):
data = [model_to_dict(row) for row in model] data = [model_to_dict(row) for row in model]
return data return data
#************************************************************************************************
# Static Accessors
#************************************************************************************************
installer = db_builder()
db_helper = db_shortcuts()

View File

@ -7,24 +7,21 @@ import os
import re import re
from functools import wraps from functools import wraps
from functools import cached_property from functools import cached_property
import peewee
from playhouse.migrate import (
SqliteMigrator,
Operation,
SQL,
SqliteDatabase,
make_index_name,
)
from app.classes.shared.helpers import helper from app.classes.shared.console import Console
from app.classes.shared.console import console from app.classes.shared.helpers import Helpers
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__) logger = logging.getLogger(__name__)
MIGRATE_TABLE = 'migratehistory' MIGRATE_TABLE = "migratehistory"
MIGRATE_TEMPLATE = '''# Generated by database migrator MIGRATE_TEMPLATE = '''# Generated by database migrator
import peewee import peewee
@ -70,6 +67,7 @@ def get_model(method):
if isinstance(model, str): if isinstance(model, str):
return method(migrator, migrator.table_dict[model], *args, **kwargs) return method(migrator, migrator.table_dict[model], *args, **kwargs)
return method(migrator, model, *args, **kwargs) return method(migrator, model, *args, **kwargs)
return wrapper return wrapper
@ -83,7 +81,7 @@ class Migrator(object):
database = database.obj database = database.obj
self.database: SqliteDatabase = database self.database: SqliteDatabase = database
self.table_dict: t.Dict[str, peewee.Model] = {} self.table_dict: t.Dict[str, peewee.Model] = {}
self.operations: t.List[t.Union[Operation, callable]] = [] self.operations: t.List[t.Union[Operation, t.Callable]] = []
self.migrator = SqliteMigrator(database) self.migrator = SqliteMigrator(database)
def run(self): def run(self):
@ -133,11 +131,17 @@ class Migrator(object):
""" """
for name, field in fields.items(): for name, field in fields.items():
model._meta.add_field(name, field) model._meta.add_field(name, field)
self.operations.append(self.migrator.add_column( self.operations.append(
model._meta.table_name, field.column_name, field)) self.migrator.add_column(
model._meta.table_name, field.column_name, field
)
)
if field.unique: if field.unique:
self.operations.append(self.migrator.add_index( self.operations.append(
model._meta.table_name, (field.column_name,), unique=True)) self.migrator.add_index(
model._meta.table_name, (field.column_name,), unique=True
)
)
return model return model
@get_model @get_model
@ -145,19 +149,22 @@ class Migrator(object):
""" """
Removes fields from model. Removes fields from model.
""" """
fields = [field for field in model._meta.fields.values() fields = [field for field in model._meta.fields.values() if field.name in names]
if field.name in names]
for field in fields: for field in fields:
self.__del_field__(model, field) self.__del_field__(model, field)
if field.unique: if field.unique:
# Drop unique index # Drop unique index
index_name = make_index_name( index_name = make_index_name(
model._meta.table_name, [field.column_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_index(model._meta.table_name, index_name)
)
self.operations.append( self.operations.append(
self.migrator.drop_column( self.migrator.drop_column(
model._meta.table_name, field.column_name, cascade=False)) model._meta.table_name, field.column_name, cascade=False
)
)
return model return model
def __del_field__(self, model: peewee.Model, field: peewee.Field): def __del_field__(self, model: peewee.Model, field: peewee.Field):
@ -169,12 +176,14 @@ class Migrator(object):
if isinstance(field, peewee.ForeignKeyField): if isinstance(field, peewee.ForeignKeyField):
obj_id_name = field.column_name obj_id_name = field.column_name
if field.column_name == field.name: if field.column_name == field.name:
obj_id_name += '_id' obj_id_name += "_id"
delattr(model, obj_id_name) delattr(model, obj_id_name)
delattr(field.rel_model, field.backref) delattr(field.rel_model, field.backref)
@get_model @get_model
def rename_column(self, model: peewee.Model, old_name: str, new_name: str) -> peewee.Model: def rename_column(
self, model: peewee.Model, old_name: str, new_name: str
) -> peewee.Model:
""" """
Renames field in model. Renames field in model.
""" """
@ -185,9 +194,10 @@ class Migrator(object):
field.name = field.column_name = new_name field.name = field.column_name = new_name
model._meta.add_field(new_name, field) model._meta.add_field(new_name, field)
if isinstance(field, peewee.ForeignKeyField): if isinstance(field, peewee.ForeignKeyField):
field.column_name = new_name = field.column_name + '_id' field.column_name = new_name = field.column_name + "_id"
self.operations.append(self.migrator.rename_column( self.operations.append(
model._meta.table_name, old_name, new_name)) self.migrator.rename_column(model._meta.table_name, old_name, new_name)
)
return model return model
@get_model @get_model
@ -203,7 +213,9 @@ class Migrator(object):
return model return model
@get_model @get_model
def add_index(self, model: peewee.Model, *columns: str, unique=False) -> peewee.Model: def add_index(
self, model: peewee.Model, *columns: str, unique=False
) -> peewee.Model:
"""Create indexes.""" """Create indexes."""
model._meta.indexes.append((columns, unique)) model._meta.indexes.append((columns, unique))
columns_ = [] columns_ = []
@ -215,11 +227,12 @@ class Migrator(object):
field.index = not unique field.index = not unique
if isinstance(field, peewee.ForeignKeyField): if isinstance(field, peewee.ForeignKeyField):
col = col + '_id' col = col + "_id"
columns_.append(col) columns_.append(col)
self.operations.append(self.migrator.add_index( self.operations.append(
model._meta.table_name, columns_, unique=unique)) self.migrator.add_index(model._meta.table_name, columns_, unique=unique)
)
return model return model
@get_model @get_model
@ -235,13 +248,15 @@ class Migrator(object):
field.unique = field.index = False field.unique = field.index = False
if isinstance(field, peewee.ForeignKeyField): if isinstance(field, peewee.ForeignKeyField):
col = col + '_id' col = col + "_id"
columns_.append(col) columns_.append(col)
index_name = make_index_name(model._meta.table_name, columns_) index_name = make_index_name(model._meta.table_name, columns_)
model._meta.indexes = [(cols, _) for ( model._meta.indexes = [
cols, _) in model._meta.indexes if columns != cols] (cols, _) for (cols, _) in model._meta.indexes if columns != cols
self.operations.append(self.migrator.drop_index( ]
model._meta.table_name, index_name)) self.operations.append(
self.migrator.drop_index(model._meta.table_name, index_name)
)
return model return model
@get_model @get_model
@ -250,8 +265,9 @@ class Migrator(object):
for name in names: for name in names:
field = model._meta.fields[name] field = model._meta.fields[name]
field.null = False field.null = False
self.operations.append(self.migrator.add_not_null( self.operations.append(
model._meta.table_name, field.column_name)) self.migrator.add_not_null(model._meta.table_name, field.column_name)
)
return model return model
@get_model @get_model
@ -260,17 +276,21 @@ class Migrator(object):
for name in names: for name in names:
field = model._meta.fields[name] field = model._meta.fields[name]
field.null = True field.null = True
self.operations.append(self.migrator.drop_not_null( self.operations.append(
model._meta.table_name, field.column_name)) self.migrator.drop_not_null(model._meta.table_name, field.column_name)
)
return model return model
@get_model @get_model
def add_default(self, model: peewee.Model, name: str, default: t.Any) -> peewee.Model: def add_default(
self, model: peewee.Model, name: str, default: t.Any
) -> peewee.Model:
"""Add default.""" """Add default."""
field = model._meta.fields[name] field = model._meta.fields[name]
model._meta.defaults[field] = field.default = default model._meta.defaults[field] = field.default = default
self.operations.append(self.migrator.apply_default( self.operations.append(
model._meta.table_name, name, field)) self.migrator.apply_default(model._meta.table_name, name, field)
)
return model return model
@ -278,13 +298,14 @@ class Migrator(object):
class MigrationManager(object): class MigrationManager(object):
filemask = re.compile(r"[\d]+_[^\.]+\.py$") filemask = re.compile(r"[\d]+_[^\.]+\.py$")
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]): def __init__(self, database: t.Union[peewee.Database, peewee.Proxy], helper):
""" """
Initializes the migration manager. Initializes the migration manager.
""" """
if not isinstance(database, (peewee.Database, peewee.Proxy)): if not isinstance(database, (peewee.Database, peewee.Proxy)):
raise RuntimeError('Invalid database: {}'.format(database)) raise RuntimeError("Invalid database: {}".format(database))
self.database = database self.database = database
self.helper = helper
@cached_property @cached_property
def model(self) -> t.Type[MigrateHistory]: def model(self) -> t.Type[MigrateHistory]:
@ -292,7 +313,7 @@ class MigrationManager(object):
Initialize and cache the MigrationHistory model. Initialize and cache the MigrationHistory model.
""" """
MigrateHistory._meta.database = self.database MigrateHistory._meta.database = self.database
MigrateHistory._meta.table_name = 'migratehistory' MigrateHistory._meta.table_name = "migratehistory"
MigrateHistory._meta.schema = None MigrateHistory._meta.schema = None
MigrateHistory.create_table(True) MigrateHistory.create_table(True)
return MigrateHistory return MigrateHistory
@ -309,11 +330,18 @@ class MigrationManager(object):
""" """
Scans migrations in the file system. Scans migrations in the file system.
""" """
if not os.path.exists(helper.migration_dir): if not os.path.exists(self.helper.migration_dir):
logger.warning('Migration directory: {} does not exist.'.format( logger.warning(
helper.migration_dir)) "Migration directory: {} does not exist.".format(
os.makedirs(helper.migration_dir) self.helper.migration_dir
return sorted(f[:-3] for f in os.listdir(helper.migration_dir) if self.filemask.match(f)) )
)
os.makedirs(self.helper.migration_dir)
return sorted(
f[:-3]
for f in os.listdir(self.helper.migration_dir)
if self.filemask.match(f)
)
@property @property
def diff(self) -> t.List[str]: def diff(self) -> t.List[str]:
@ -333,24 +361,27 @@ class MigrationManager(object):
self.up_one(name, migrator, True) self.up_one(name, migrator, True)
return migrator return migrator
def compile(self, name, migrate='', rollback=''): def compile(self, name, migrate="", rollback=""):
""" """
Compiles a migration. Compiles a migration.
""" """
name = datetime.utcnow().strftime('%Y%m%d%H%M%S') + '_' + name name = datetime.utcnow().strftime("%Y%m%d%H%M%S") + "_" + name
filename = name + '.py' filename = name + ".py"
path = os.path.join(helper.migration_dir, filename) path = os.path.join(self.helper.migration_dir, filename)
with open(path, 'w') as f: with open(path, "w") as f:
f.write(MIGRATE_TEMPLATE.format( f.write(
migrate=migrate, rollback=rollback, name=filename)) MIGRATE_TEMPLATE.format(
migrate=migrate, rollback=rollback, name=filename
)
)
return name return name
def create(self, name: str = 'auto', auto: bool = False) -> t.Optional[str]: def create(self, name: str = "auto", auto: bool = False) -> t.Optional[str]:
""" """
Creates a migration. Creates a migration.
""" """
migrate = rollback = '' migrate = rollback = ""
if auto: if auto:
raise NotImplementedError raise NotImplementedError
@ -367,14 +398,14 @@ class MigrationManager(object):
""" """
Runs all unapplied migrations. Runs all unapplied migrations.
""" """
logger.info('Starting migrations') logger.info("Starting migrations")
console.info('Starting migrations') Console.info("Starting migrations")
done = [] done = []
diff = self.diff diff = self.diff
if not diff: if not diff:
logger.info('There is nothing to migrate') logger.info("There is nothing to migrate")
console.info('There is nothing to migrate') Console.info("There is nothing to migrate")
return done return done
migrator = self.migrator migrator = self.migrator
@ -390,18 +421,23 @@ class MigrationManager(object):
Reads a migration from a file. Reads a migration from a file.
""" """
call_params = dict() call_params = dict()
if helper.is_os_windows() and sys.version_info >= (3, 0): if Helpers.is_os_windows() and sys.version_info >= (3, 0):
# if system is windows - force utf-8 encoding # if system is windows - force utf-8 encoding
call_params['encoding'] = 'utf-8' call_params["encoding"] = "utf-8"
with open(os.path.join(helper.migration_dir, name + '.py'), **call_params) as f: with open(
os.path.join(self.helper.migration_dir, name + ".py"), **call_params
) as f:
code = f.read() code = f.read()
scope = {} scope = {}
code = compile(code, '<string>', 'exec', dont_inherit=True) code = compile(code, "<string>", "exec", dont_inherit=True)
exec(code, scope, None) exec(code, scope, None)
return scope.get('migrate', lambda m, d: None), scope.get('rollback', lambda m, d: None) return scope.get("migrate", lambda m, d: None), scope.get(
"rollback", lambda m, d: None
)
def up_one(self, name: str, migrator: Migrator, def up_one(
fake: bool = False, rollback: bool = False) -> str: self, name: str, migrator: Migrator, fake: bool = False, rollback: bool = False
) -> str:
""" """
Runs a migration with a given name. Runs a migration with a given name.
""" """
@ -429,8 +465,8 @@ class MigrationManager(object):
except Exception: except Exception:
self.database.rollback() self.database.rollback()
operation_name = 'Rollback' if rollback else 'Migration' operation_name = "Rollback" if rollback else "Migration"
logger.exception('{} failed: {}'.format(operation_name, name)) logger.exception("{} failed: {}".format(operation_name, name))
raise raise
def down(self): def down(self):
@ -438,10 +474,10 @@ class MigrationManager(object):
Rolls back migrations. Rolls back migrations.
""" """
if not self.done: if not self.done:
raise RuntimeError('No migrations are found.') raise RuntimeError("No migrations are found.")
name = self.done[-1] name = self.done[-1]
migrator = self.migrator migrator = self.migrator
self.up_one(name, migrator, False, True) self.up_one(name, migrator, False, True)
logger.warning('Rolled back migration: {}'.format(name)) logger.warning("Rolled back migration: {}".format(name))

View File

@ -1,22 +1,27 @@
from enum import Enum from enum import Enum
class PermissionHelper: class PermissionHelper:
@staticmethod @staticmethod
def both_have_perm(a: str, b: str, permission_tested: Enum): def both_have_perm(
return permission_helper.combine_perm_bool(a[permission_tested.value], b[permission_tested.value]) permission_mask_a: str, permission_mask_b: str, permission_tested: Enum
):
return PermissionHelper.combine_perm_bool(
permission_mask_a[permission_tested.value],
permission_mask_b[permission_tested.value],
)
@staticmethod @staticmethod
def combine_perm(a: str, b: str) -> str: def combine_perm(permission_mask_a: str, permission_mask_b: str) -> str:
return '1' if (a == '1' and b == '1') else '0' return "1" if (permission_mask_a == "1" and permission_mask_b == "1") else "0"
@staticmethod @staticmethod
def combine_perm_bool(a: str, b: str) -> bool: def combine_perm_bool(permission_mask_a: str, permission_mask_b: str) -> bool:
return a == '1' and b == '1' return permission_mask_a == "1" and permission_mask_b == "1"
@staticmethod @staticmethod
def combine_masks(permission_mask_a: str, permission_mask_b: str) -> str: def combine_masks(permission_mask_a: str, permission_mask_b: str) -> str:
both_masks = zip(list(permission_mask_a), list(permission_mask_b)) 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)) return "".join(
map(lambda x: PermissionHelper.combine_perm(x[0], x[1]), both_masks)
)
permission_helper = PermissionHelper()

File diff suppressed because it is too large Load Diff

View File

@ -4,71 +4,76 @@ import logging
import threading import threading
import asyncio import asyncio
import datetime import datetime
from tzlocal import get_localzone
from apscheduler.events import EVENT_JOB_EXECUTED
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from app.classes.controllers.users_controller import Users_Controller from app.classes.models.management import HelpersManagement
from app.classes.minecraft.serverjars import server_jar_obj from app.classes.models.users import HelperUsers
from app.classes.models.management import management_helper from app.classes.shared.console import Console
from app.classes.models.users import users_helper from app.classes.shared.main_controller import Controller
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.web.tornado_handler import Webserver from app.classes.web.tornado_handler import Webserver
from app.classes.web.websocket_helper import websocket_helper
try: logger = logging.getLogger("apscheduler")
from tzlocal import get_localzone scheduler_intervals = {
from apscheduler.events import EVENT_JOB_EXECUTED "seconds",
from apscheduler.schedulers.background import BackgroundScheduler "minutes",
from apscheduler.triggers.cron import CronTrigger "hours",
"days",
"weeks",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
}
except ModuleNotFoundError as err:
helper.auto_installer_fix(err)
logger = logging.getLogger('apscheduler')
scheduler_intervals = { 'seconds',
'minutes',
'hours',
'days',
'weeks',
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday'
}
class TasksManager: class TasksManager:
controller: Controller
def __init__(self, controller): def __init__(self, helper, controller):
self.helper = helper
self.controller = controller self.controller = controller
self.tornado = Webserver(controller, self) self.tornado = Webserver(helper, controller, self)
self.tz = get_localzone() self.tz = get_localzone()
self.scheduler = BackgroundScheduler(timezone=str(self.tz)) self.scheduler = BackgroundScheduler(timezone=str(self.tz))
self.users_controller = Users_Controller() self.users_controller = self.controller.users
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread') self.webserver_thread = threading.Thread(
target=self.tornado.run_tornado, daemon=True, name="tornado_thread"
)
self.main_thread_exiting = False self.main_thread_exiting = False
self.schedule_thread = threading.Thread(target=self.scheduler_thread, daemon=True, name="scheduler") self.schedule_thread = threading.Thread(
target=self.scheduler_thread, daemon=True, name="scheduler"
)
self.log_watcher_thread = threading.Thread(target=self.log_watcher, daemon=True, name="log_watcher") self.log_watcher_thread = threading.Thread(
target=self.log_watcher, daemon=True, name="log_watcher"
)
self.command_thread = threading.Thread(target=self.command_watcher, daemon=True, name="command_watcher") self.command_thread = threading.Thread(
target=self.command_watcher, daemon=True, name="command_watcher"
)
self.realtime_thread = threading.Thread(target=self.realtime, daemon=True, name="realtime") self.realtime_thread = threading.Thread(
target=self.realtime, daemon=True, name="realtime"
)
self.reload_schedule_from_db() self.reload_schedule_from_db()
def get_main_thread_run_status(self): def get_main_thread_run_status(self):
return self.main_thread_exiting return self.main_thread_exiting
def reload_schedule_from_db(self): def reload_schedule_from_db(self):
jobs = management_helper.get_schedules_enabled() jobs = HelpersManagement.get_schedules_enabled()
logger.info("Reload from DB called. Current enabled schedules: ") logger.info("Reload from DB called. Current enabled schedules: ")
for item in jobs: for item in jobs:
logger.info(f"JOB: {item}") logger.info(f"JOB: {item}")
@ -76,26 +81,40 @@ class TasksManager:
def command_watcher(self): def command_watcher(self):
while True: while True:
# select any commands waiting to be processed # select any commands waiting to be processed
commands = management_helper.get_unactioned_commands() commands = HelpersManagement.get_unactioned_commands()
for c in commands: for cmd in commands:
try: try:
svr = self.controller.get_server_obj(c.server_id) svr = self.controller.get_server_obj(cmd.server_id)
except: except:
logger.error("Server value requested does note exist purging item from waiting commands.") logger.error(
management_helper.mark_command_complete(c.command_id) "Server value requested does note exist! "
"Purging item from waiting commands."
)
HelpersManagement.mark_command_complete(cmd.command_id)
user_id = c.user_id user_id = cmd.user_id
command = c.command command = cmd.command
if command == 'start_server': if command == "start_server":
svr.run_threaded_server(user_id) svr.run_threaded_server(user_id)
elif command == 'stop_server': elif command == "stop_server":
svr.stop_threaded_server() svr.stop_threaded_server()
elif command == "restart_server": elif command == "restart_server":
svr.restart_threaded_server(user_id) svr.restart_threaded_server(user_id)
elif command == "kill_server":
try:
svr.kill()
time.sleep(5)
svr.cleanup_server_object()
svr.record_server_stats()
except Exception as e:
logger.error(
f"Could not find PID for requested termsig. Full error: {e}"
)
elif command == "backup_server": elif command == "backup_server":
svr.backup_server() svr.backup_server()
@ -103,19 +122,19 @@ class TasksManager:
svr.jar_update() svr.jar_update()
else: else:
svr.send_command(command) svr.send_command(command)
management_helper.mark_command_complete(c.command_id) HelpersManagement.mark_command_complete(cmd.command_id)
time.sleep(1) time.sleep(1)
def _main_graceful_exit(self): def _main_graceful_exit(self):
try: try:
os.remove(helper.session_file) os.remove(self.helper.session_file)
self.controller.stop_all_servers() self.controller.stop_all_servers()
except: except:
logger.info("Caught error during shutdown", exc_info=True) logger.info("Caught error during shutdown", exc_info=True)
logger.info("***** Crafty Shutting Down *****\n\n") logger.info("***** Crafty Shutting Down *****\n\n")
console.info("***** Crafty Shutting Down *****\n\n") Console.info("***** Crafty Shutting Down *****\n\n")
self.main_thread_exiting = True self.main_thread_exiting = True
def start_webserver(self): def start_webserver(self):
@ -123,9 +142,11 @@ class TasksManager:
def reload_webserver(self): def reload_webserver(self):
self.tornado.stop_web_server() self.tornado.stop_web_server()
console.info("Waiting 3 seconds") Console.info("Waiting 3 seconds")
time.sleep(3) time.sleep(3)
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread') self.webserver_thread = threading.Thread(
target=self.tornado.run_tornado, daemon=True, name="tornado_thread"
)
self.start_webserver() self.start_webserver()
def stop_webserver(self): def stop_webserver(self):
@ -133,79 +154,96 @@ class TasksManager:
def start_scheduler(self): def start_scheduler(self):
logger.info("Launching Scheduler Thread...") logger.info("Launching Scheduler Thread...")
console.info("Launching Scheduler Thread...") Console.info("Launching Scheduler Thread...")
self.schedule_thread.start() self.schedule_thread.start()
logger.info("Launching command thread...") logger.info("Launching command thread...")
console.info("Launching command thread...") Console.info("Launching command thread...")
self.command_thread.start() self.command_thread.start()
logger.info("Launching log watcher...") logger.info("Launching log watcher...")
console.info("Launching log watcher...") Console.info("Launching log watcher...")
self.log_watcher_thread.start() self.log_watcher_thread.start()
logger.info("Launching realtime thread...") logger.info("Launching realtime thread...")
console.info("Launching realtime thread...") Console.info("Launching realtime thread...")
self.realtime_thread.start() self.realtime_thread.start()
def scheduler_thread(self): def scheduler_thread(self):
schedules = management_helper.get_schedules_enabled() schedules = HelpersManagement.get_schedules_enabled()
self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED) self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
#self.scheduler.add_job(self.scheduler.print_jobs, 'interval', seconds=10, id='-1') # self.scheduler.add_job(
# self.scheduler.print_jobs, "interval", seconds=10, id="-1"
# )
#load schedules from DB # load schedules from DB
for schedule in schedules: for schedule in schedules:
if schedule.interval != 'reaction': if schedule.interval != "reaction":
if schedule.cron_string != "": if schedule.cron_string != "":
try: try:
self.scheduler.add_job(management_helper.add_command, self.scheduler.add_job(
CronTrigger.from_crontab(schedule.cron_string, HelpersManagement.add_command,
timezone=str(self.tz)), CronTrigger.from_crontab(
id = str(schedule.schedule_id), schedule.cron_string, timezone=str(self.tz)
args = [schedule.server_id, ),
self.users_controller.get_id_by_name('system'), id=str(schedule.schedule_id),
'127.0.0.1', args=[
schedule.command] schedule.server_id,
) self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
],
)
except Exception as e: except Exception as e:
console.error(f"Failed to schedule task with error: {e}.") Console.error(f"Failed to schedule task with error: {e}.")
console.warning("Removing failed task from DB.") Console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.") logger.error(f"Failed to schedule task with error: {e}.")
logger.warning("Removing failed task from DB.") logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler # remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(schedule.schedule_id) self.controller.management_helper.delete_scheduled_task(
schedule.schedule_id
)
else: else:
if schedule.interval_type == 'hours': if schedule.interval_type == "hours":
self.scheduler.add_job(management_helper.add_command, self.scheduler.add_job(
'cron', HelpersManagement.add_command,
minute = 0, "cron",
hour = '*/'+str(schedule.interval), minute=0,
id = str(schedule.schedule_id), hour="*/" + str(schedule.interval),
args = [schedule.server_id, id=str(schedule.schedule_id),
self.users_controller.get_id_by_name('system'), args=[
'127.0.0.1', schedule.server_id,
schedule.command] self.users_controller.get_id_by_name("system"),
) "127.0.0.1",
elif schedule.interval_type == 'minutes': schedule.command,
self.scheduler.add_job(management_helper.add_command, ],
'cron', )
minute = '*/'+str(schedule.interval), elif schedule.interval_type == "minutes":
id = str(schedule.schedule_id), self.scheduler.add_job(
args = [schedule.server_id, HelpersManagement.add_command,
self.users_controller.get_id_by_name('system'), "cron",
'127.0.0.1', minute="*/" + str(schedule.interval),
schedule.command] id=str(schedule.schedule_id),
) args=[
elif schedule.interval_type == 'days': schedule.server_id,
curr_time = schedule.start_time.split(':') self.users_controller.get_id_by_name("system"),
self.scheduler.add_job(management_helper.add_command, "127.0.0.1",
'cron', schedule.command,
day = '*/'+str(schedule.interval), ],
hour=curr_time[0], )
minute=curr_time[1], elif schedule.interval_type == "days":
id=str(schedule.schedule_id), curr_time = schedule.start_time.split(":")
args=[schedule.server_id, self.scheduler.add_job(
self.users_controller.get_id_by_name('system'), HelpersManagement.add_command,
'127.0.0.1', "cron",
schedule.command] 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() self.scheduler.start()
jobs = self.scheduler.get_jobs() jobs = self.scheduler.get_jobs()
logger.info("Loaded schedules. Current enabled schedules: ") logger.info("Loaded schedules. Current enabled schedules: ")
@ -213,240 +251,332 @@ class TasksManager:
logger.info(f"JOB: {item}") logger.info(f"JOB: {item}")
def schedule_job(self, job_data): def schedule_job(self, job_data):
sch_id = management_helper.create_scheduled_task( sch_id = HelpersManagement.create_scheduled_task(
job_data['server_id'], job_data["server_id"],
job_data['action'], job_data["action"],
job_data['interval'], job_data["interval"],
job_data['interval_type'], job_data["interval_type"],
job_data['start_time'], job_data["start_time"],
job_data['command'], job_data["command"],
"None", "None",
job_data['enabled'], job_data["enabled"],
job_data['one_time'], job_data["one_time"],
job_data['cron_string'], job_data["cron_string"],
job_data['parent'], job_data["parent"],
job_data['delay']) 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): # Checks to make sure some doofus didn't actually make the newly
management_helper.update_scheduled_task(sch_id, {'parent':None}) # created task a child of itself.
#Check to see if it's enabled and is not a chain reaction. if str(job_data["parent"]) == str(sch_id):
if job_data['enabled'] and job_data['interval_type'] != 'reaction': HelpersManagement.update_scheduled_task(sch_id, {"parent": None})
if job_data['cron_string'] != "": # 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: try:
self.scheduler.add_job(management_helper.add_command, self.scheduler.add_job(
CronTrigger.from_crontab(job_data['cron_string'], HelpersManagement.add_command,
timezone=str(self.tz)), CronTrigger.from_crontab(
id=str(sch_id), job_data["cron_string"], timezone=str(self.tz)
args=[job_data['server_id'], ),
self.users_controller.get_id_by_name('system'), id=str(sch_id),
'127.0.0.1', args=[
job_data['command']] job_data["server_id"],
) self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
except Exception as e: except Exception as e:
console.error(f"Failed to schedule task with error: {e}.") Console.error(f"Failed to schedule task with error: {e}.")
console.warning("Removing failed task from DB.") Console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.") logger.error(f"Failed to schedule task with error: {e}.")
logger.warning("Removing failed task from DB.") logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler # remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(sch_id) self.controller.management_helper.delete_scheduled_task(sch_id)
else: else:
if job_data['interval_type'] == 'hours': if job_data["interval_type"] == "hours":
self.scheduler.add_job(management_helper.add_command, self.scheduler.add_job(
'cron', HelpersManagement.add_command,
minute = 0, "cron",
hour = '*/'+str(job_data['interval']), minute=0,
id=str(sch_id), hour="*/" + str(job_data["interval"]),
args=[job_data['server_id'], id=str(sch_id),
self.users_controller.get_id_by_name('system'), args=[
'127.0.0.1', job_data["server_id"],
job_data['command']] self.users_controller.get_id_by_name("system"),
) "127.0.0.1",
elif job_data['interval_type'] == 'minutes': job_data["command"],
self.scheduler.add_job(management_helper.add_command, ],
'cron', )
minute = '*/'+str(job_data['interval']), elif job_data["interval_type"] == "minutes":
id=str(sch_id), self.scheduler.add_job(
args=[job_data['server_id'], HelpersManagement.add_command,
self.users_controller.get_id_by_name('system'), "cron",
'127.0.0.1', minute="*/" + str(job_data["interval"]),
job_data['command']] id=str(sch_id),
) args=[
elif job_data['interval_type'] == 'days': job_data["server_id"],
curr_time = job_data['start_time'].split(':') self.users_controller.get_id_by_name("system"),
self.scheduler.add_job(management_helper.add_command, "127.0.0.1",
'cron', job_data["command"],
day = '*/'+str(job_data['interval']), ],
hour = curr_time[0], )
minute = curr_time[1], elif job_data["interval_type"] == "days":
id=str(sch_id), curr_time = job_data["start_time"].split(":")
args=[job_data['server_id'], self.scheduler.add_job(
self.users_controller.get_id_by_name('system'), HelpersManagement.add_command,
'127.0.0.1', "cron",
job_data['command']], 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: ") logger.info("Added job. Current enabled schedules: ")
jobs = self.scheduler.get_jobs() jobs = self.scheduler.get_jobs()
for item in jobs: for item in jobs:
logger.info(f"JOB: {item}") logger.info(f"JOB: {item}")
def remove_all_server_tasks(self, server_id): def remove_all_server_tasks(self, server_id):
schedules = management_helper.get_schedules_by_server(server_id) schedules = HelpersManagement.get_schedules_by_server(server_id)
for schedule in schedules: for schedule in schedules:
if schedule.interval != 'reaction': if schedule.interval != "reaction":
self.remove_job(schedule.schedule_id) self.remove_job(schedule.schedule_id)
def remove_job(self, sch_id): def remove_job(self, sch_id):
job = management_helper.get_scheduled_task_model(sch_id) job = HelpersManagement.get_scheduled_task_model(sch_id)
for schedule in management_helper.get_child_schedules(sch_id): for schedule in HelpersManagement.get_child_schedules(sch_id):
management_helper.update_scheduled_task(schedule.schedule_id, {'parent':None}) self.controller.management_helper.update_scheduled_task(
management_helper.delete_scheduled_task(sch_id) schedule.schedule_id, {"parent": None}
if job.enabled and job.interval_type != 'reaction': )
self.controller.management_helper.delete_scheduled_task(sch_id)
if job.enabled and job.interval_type != "reaction":
self.scheduler.remove_job(str(sch_id)) self.scheduler.remove_job(str(sch_id))
logger.info(f"Job with ID {sch_id} was deleted.") logger.info(f"Job with ID {sch_id} was deleted.")
else: else:
logger.info(f"Job with ID {sch_id} was deleted from DB, but was not enabled." logger.info(
+ "Not going to try removing something that doesn't exist from active schedules.") f"Job with ID {sch_id} was deleted from DB, but was not enabled."
f"Not going to try removing something "
f"that doesn't exist from active schedules."
)
def update_job(self, sch_id, job_data): def update_job(self, sch_id, job_data):
management_helper.update_scheduled_task(sch_id, job_data) HelpersManagement.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. # Checks to make sure some doofus didn't actually make the newly
if str(job_data['parent']) == str(sch_id): # created task a child of itself.
management_helper.update_scheduled_task(sch_id, {'parent':None}) if str(job_data["parent"]) == str(sch_id):
HelpersManagement.update_scheduled_task(sch_id, {"parent": None})
try: try:
if job_data['interval'] != 'reaction': if job_data["interval"] != "reaction":
self.scheduler.remove_job(str(sch_id)) self.scheduler.remove_job(str(sch_id))
except: except:
logger.info("No job found in update job. Assuming it was previously disabled. Starting new job.") logger.info(
"No job found in update job. "
"Assuming it was previously disabled. Starting new job."
)
if job_data['enabled']: if job_data["enabled"]:
if job_data['interval'] != 'reaction': if job_data["interval"] != "reaction":
if job_data['cron_string'] != "": if job_data["cron_string"] != "":
try: try:
self.scheduler.add_job(management_helper.add_command, self.scheduler.add_job(
CronTrigger.from_crontab(job_data['cron_string'], HelpersManagement.add_command,
timezone=str(self.tz)), CronTrigger.from_crontab(
id=str(sch_id), job_data["cron_string"], timezone=str(self.tz)
args=[job_data['server_id'], ),
self.users_controller.get_id_by_name('system'), id=str(sch_id),
'127.0.0.1', args=[
job_data['command']] job_data["server_id"],
) self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
except Exception as e: except Exception as e:
console.error(f"Failed to schedule task with error: {e}.") Console.error(f"Failed to schedule task with error: {e}.")
console.info("Removing failed task from DB.") Console.info("Removing failed task from DB.")
management_helper.delete_scheduled_task(sch_id) self.controller.management_helper.delete_scheduled_task(sch_id)
else: else:
if job_data['interval_type'] == 'hours': if job_data["interval_type"] == "hours":
self.scheduler.add_job(management_helper.add_command, self.scheduler.add_job(
'cron', HelpersManagement.add_command,
minute = 0, "cron",
hour = '*/'+str(job_data['interval']), minute=0,
id=str(sch_id), hour="*/" + str(job_data["interval"]),
args=[job_data['server_id'], id=str(sch_id),
self.users_controller.get_id_by_name('system'), args=[
'127.0.0.1', job_data["server_id"],
job_data['command']] self.users_controller.get_id_by_name("system"),
) "127.0.0.1",
elif job_data['interval_type'] == 'minutes': job_data["command"],
self.scheduler.add_job(management_helper.add_command, ],
'cron', )
minute = '*/'+str(job_data['interval']), elif job_data["interval_type"] == "minutes":
id=str(sch_id), self.scheduler.add_job(
args=[job_data['server_id'], HelpersManagement.add_command,
self.users_controller.get_id_by_name('system'), "cron",
'127.0.0.1', minute="*/" + str(job_data["interval"]),
job_data['command']] id=str(sch_id),
) args=[
elif job_data['interval_type'] == 'days': job_data["server_id"],
curr_time = job_data['start_time'].split(':') self.users_controller.get_id_by_name("system"),
self.scheduler.add_job(management_helper.add_command, "127.0.0.1",
'cron', job_data["command"],
day = '*/'+str(job_data['interval']), ],
hour = curr_time[0], )
minute = curr_time[1], elif job_data["interval_type"] == "days":
id=str(sch_id), curr_time = job_data["start_time"].split(":")
args=[job_data['server_id'], self.scheduler.add_job(
self.users_controller.get_id_by_name('system'), HelpersManagement.add_command,
'127.0.0.1', "cron",
job_data['command']] 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: else:
try: try:
self.scheduler.get_job(str(sch_id)) self.scheduler.get_job(str(sch_id))
self.scheduler.remove_job(str(sch_id)) self.scheduler.remove_job(str(sch_id))
except: except:
logger.info(f"APScheduler found no scheduled job on schedule update for schedule with id: {sch_id} Assuming it was already disabled.") logger.info(
f"APScheduler found no scheduled job on schedule update for "
f"schedule with id: {sch_id} Assuming it was already disabled."
)
def schedule_watcher(self, event): def schedule_watcher(self, event):
if not event.exception: if not event.exception:
if str(event.job_id).isnumeric(): if str(event.job_id).isnumeric():
task = management_helper.get_scheduled_task_model(int(event.job_id)) task = self.controller.management.get_scheduled_task_model(
management_helper.add_to_audit_log_raw('system', users_helper.get_user_id_by_name('system'), task.server_id, int(event.job_id)
f"Task with id {task.schedule_id} completed successfully", '127.0.0.1') )
#check if the task is a single run. self.controller.management.add_to_audit_log_raw(
"system",
HelperUsers.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: if task.one_time:
self.remove_job(task.schedule_id) self.remove_job(task.schedule_id)
logger.info("one time task detected. Deleting...") 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. # check for any child tasks for this. It's kind of backward,
for schedule in management_helper.get_child_schedules_by_server(task.schedule_id, task.server_id): # but this makes DB management a lot easier. One to one
#event job ID's are strings so we need to look at this as the same data type. # instead of one to many.
for schedule in HelpersManagement.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 str(schedule.parent) == str(event.job_id):
if schedule.enabled: if schedule.enabled:
delaytime = datetime.datetime.now() + datetime.timedelta(seconds=schedule.delay) delaytime = datetime.datetime.now() + datetime.timedelta(
self.scheduler.add_job(management_helper.add_command, 'date', run_date=delaytime, id=str(schedule.schedule_id), seconds=schedule.delay
args=[schedule.server_id, )
self.users_controller.get_id_by_name('system'), self.scheduler.add_job(
'127.0.0.1', HelpersManagement.add_command,
schedule.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: else:
logger.info("Event job ID is not numerical. Assuming it's stats - not stored in DB. Moving on.") logger.info(
"Event job ID is not numerical. Assuming it's stats "
"- not stored in DB. Moving on."
)
else: else:
logger.error(f"Task failed with error: {event.exception}") logger.error(f"Task failed with error: {event.exception}")
def start_stats_recording(self): def start_stats_recording(self):
stats_update_frequency = helper.get_setting('stats_update_frequency') stats_update_frequency = self.helper.get_setting("stats_update_frequency")
logger.info(f"Stats collection frequency set to {stats_update_frequency} seconds") logger.info(
console.info(f"Stats collection frequency set to {stats_update_frequency} seconds") 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, # one for now,
self.controller.stats.record_stats() self.controller.stats.record_stats()
# one for later # one for later
self.scheduler.add_job(self.controller.stats.record_stats, 'interval', seconds=stats_update_frequency, id="stats") self.scheduler.add_job(
self.controller.stats.record_stats,
"interval",
seconds=stats_update_frequency,
id="stats",
)
def serverjar_cache_refresher(self): def serverjar_cache_refresher(self):
logger.info("Refreshing serverjars.com cache on start") logger.info("Refreshing serverjars.com cache on start")
server_jar_obj.refresh_cache() self.controller.server_jars.refresh_cache()
logger.info("Scheduling Serverjars.com cache refresh service every 12 hours") logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
self.scheduler.add_job(server_jar_obj.refresh_cache, 'interval', hours=12, id="serverjars") self.scheduler.add_job(
self.controller.server_jars.refresh_cache,
"interval",
hours=12,
id="serverjars",
)
def realtime(self): def realtime(self):
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
host_stats = management_helper.get_latest_hosts_stats() host_stats = HelpersManagement.get_latest_hosts_stats()
while True: while True:
if host_stats.get('cpu_usage') != \ if host_stats.get(
management_helper.get_latest_hosts_stats().get('cpu_usage') or \ "cpu_usage"
host_stats.get('mem_percent') != \ ) != HelpersManagement.get_latest_hosts_stats().get(
management_helper.get_latest_hosts_stats().get('mem_percent'): "cpu_usage"
) or host_stats.get(
"mem_percent"
) != HelpersManagement.get_latest_hosts_stats().get(
"mem_percent"
):
# Stats are different # Stats are different
host_stats = management_helper.get_latest_hosts_stats() host_stats = HelpersManagement.get_latest_hosts_stats()
if len(websocket_helper.clients) > 0: if len(self.helper.websocket_helper.clients) > 0:
# There are clients # There are clients
websocket_helper.broadcast_page('/panel/dashboard', 'update_host_stats', { self.helper.websocket_helper.broadcast_page(
'cpu_usage': host_stats.get('cpu_usage'), "/panel/dashboard",
'cpu_cores': host_stats.get('cpu_cores'), "update_host_stats",
'cpu_cur_freq': host_stats.get('cpu_cur_freq'), {
'cpu_max_freq': host_stats.get('cpu_max_freq'), "cpu_usage": host_stats.get("cpu_usage"),
'mem_percent': host_stats.get('mem_percent'), "cpu_cores": host_stats.get("cpu_cores"),
'mem_usage': host_stats.get('mem_usage') "cpu_cur_freq": host_stats.get("cpu_cur_freq"),
}) "cpu_max_freq": host_stats.get("cpu_max_freq"),
"mem_percent": host_stats.get("mem_percent"),
"mem_usage": host_stats.get("mem_usage"),
},
)
time.sleep(1)
def log_watcher(self): def log_watcher(self):
self.controller.servers.check_for_old_logs() self.controller.servers.check_for_old_logs()
self.scheduler.add_job(self.controller.servers.check_for_old_logs, 'interval', hours=6, id="log-mgmt") self.scheduler.add_job(
self.controller.servers.check_for_old_logs,
"interval",
hours=6,
id="log-mgmt",
)

View File

@ -3,22 +3,25 @@ import logging
import os import os
import typing as t import typing as t
from app.classes.shared.console import console from app.classes.shared.console import Console
from app.classes.shared.helpers import helper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Translation: class Translation:
def __init__(self): def __init__(self, helper):
self.translations_path = os.path.join(helper.root_dir, 'app', 'translations') self.helper = helper
self.translations_path = os.path.join(
self.helper.root_dir, "app", "translations"
)
self.cached_translation = None self.cached_translation = None
self.cached_translation_lang = None self.cached_translation_lang = None
def get_language_file(self, language: str): def get_language_file(self, language: str):
return os.path.join(self.translations_path, str(language) + '.json') return os.path.join(self.translations_path, str(language) + ".json")
def translate(self, page, word, language): def translate(self, page, word, language):
fallback_language = 'en_EN' fallback_language = "en_EN"
translated_word = self.translate_inner(page, word, language) translated_word = self.translate_inner(page, word, language)
if translated_word is None: if translated_word is None:
@ -31,20 +34,20 @@ class Translation:
elif isinstance(translated_word, str): elif isinstance(translated_word, str):
# Basic strings # Basic strings
return translated_word return translated_word
elif hasattr(translated_word, '__iter__'): elif hasattr(translated_word, "__iter__"):
# Multiline strings # Multiline strings
return '\n'.join(translated_word) return "\n".join(translated_word)
return 'Error while getting translation' return "Error while getting translation"
def translate_inner(self, page, word, language) -> t.Union[t.Any, None]: def translate_inner(self, page, word, language) -> t.Union[t.Any, None]:
language_file = self.get_language_file(language) language_file = self.get_language_file(language)
try: try:
if not self.cached_translation: if not self.cached_translation:
with open(language_file, 'r', encoding='utf-8') as f: with open(language_file, "r", encoding="utf-8") as f:
data = json.load(f) data = json.load(f)
self.cached_translation = data self.cached_translation = data
elif self.cached_translation_lang != language: elif self.cached_translation_lang != language:
with open(language_file, 'r', encoding='utf-8') as f: with open(language_file, "r", encoding="utf-8") as f:
data = json.load(f) data = json.load(f)
self.cached_translation = data self.cached_translation = data
self.cached_translation_lang = language self.cached_translation_lang = language
@ -54,22 +57,35 @@ class Translation:
try: try:
translated_page = data[page] translated_page = data[page]
except KeyError: except KeyError:
logger.error(f'Translation File Error: page {page} does not exist for lang {language}') logger.error(
console.error(f'Translation File Error: page {page} does not exist for lang {language}') f"Translation File Error: page {page} "
f"does not exist for lang {language}"
)
Console.error(
f"Translation File Error: page {page} "
f"does not exist for lang {language}"
)
return None return None
try: try:
translated_word = translated_page[word] translated_word = translated_page[word]
return translated_word return translated_word
except KeyError: except KeyError:
logger.error(f'Translation File Error: word {word} does not exist on page {page} for lang {language}') logger.error(
console.error(f'Translation File Error: word {word} does not exist on page {page} for lang {language}') f"Translation File Error: word {word} does not exist on page "
f"{page} for lang {language}"
)
Console.error(
f"Translation File Error: word {word} does not exist on page "
f"{page} for lang {language}"
)
return None return None
except Exception as e: except Exception as e:
logger.critical(f'Translation File Error: Unable to read {language_file} due to {e}') logger.critical(
console.critical(f'Translation File Error: Unable to read {language_file} due to {e}') 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 return None
translation = Translation()

View File

@ -3,27 +3,20 @@ import html
import re import re
import logging import logging
import time import time
import bleach
import tornado.web
import tornado.escape
from app.classes.models.server_permissions import Enum_Permissions_Server from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.shared.console import console from app.classes.shared.console import Console
from app.classes.shared.helpers import helper from app.classes.shared.helpers import Helpers
from app.classes.shared.translation import translation
from app.classes.shared.server import ServerOutBuf from app.classes.shared.server import ServerOutBuf
from app.classes.web.websocket_helper import websocket_helper
from app.classes.web.base_handler import BaseHandler 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__) logger = logging.getLogger(__name__)
class AjaxHandler(BaseHandler):
class AjaxHandler(BaseHandler):
def render_page(self, template, page_data): def render_page(self, template, page_data):
self.render( self.render(
template, template,
@ -34,22 +27,19 @@ class AjaxHandler(BaseHandler):
@tornado.web.authenticated @tornado.web.authenticated
def get(self, page): def get(self, page):
_, _, exec_user = self.current_user _, _, exec_user = self.current_user
error = bleach.clean(self.get_argument('error', "WTF Error!")) error = bleach.clean(self.get_argument("error", "WTF Error!"))
template = "panel/denied.html" template = "panel/denied.html"
page_data = { page_data = {"user_data": exec_user, "error": error}
'user_data': exec_user,
'error': error
}
if page == "error": if page == "error":
template = "public/error.html" template = "public/error.html"
self.render_page(template, page_data) self.render_page(template, page_data)
elif page == 'server_log': elif page == "server_log":
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
full_log = self.get_argument('full', False) full_log = self.get_argument("full", False)
if server_id is None: if server_id is None:
logger.warning("Server ID not found in server_log ajax call") logger.warning("Server ID not found in server_log ajax call")
@ -64,50 +54,59 @@ class AjaxHandler(BaseHandler):
self.redirect("/panel/error?error=Server ID Not Found") self.redirect("/panel/error?error=Server ID Not Found")
return return
if not server_data['log_path']: if not server_data["log_path"]:
logger.warning(f"Log path not found in server_log ajax call ({server_id})") logger.warning(
f"Log path not found in server_log ajax call ({server_id})"
)
if full_log: if full_log:
log_lines = helper.get_setting('max_log_lines') log_lines = self.helper.get_setting("max_log_lines")
data = helper.tail_file(helper.get_os_understandable_path(server_data['log_path']), log_lines) data = Helpers.tail_file(
Helpers.get_os_understandable_path(server_data["log_path"]),
log_lines,
)
else: else:
data = ServerOutBuf.lines.get(server_id, []) data = ServerOutBuf.lines.get(server_id, [])
for line in data:
for d in data:
try: try:
d = re.sub('(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )', '', d) line = re.sub("(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)", "", line)
d = re.sub('[A-z]{2}\b\b', '', d) line = re.sub("[A-z]{2}\b\b", "", line)
line = helper.log_colors(html.escape(d)) line = self.helper.log_colors(html.escape(line))
self.write(f'{line}<br />') self.write(f"{line}<br />")
# self.write(d.encode("utf-8")) # self.write(d.encode("utf-8"))
except Exception as e: except Exception as e:
logger.warning(f"Skipping Log Line due to error: {e}") logger.warning(f"Skipping Log Line due to error: {e}")
elif page == "announcements": elif page == "announcements":
data = helper.get_announcements() data = Helpers.get_announcements()
page_data['notify_data'] = data page_data["notify_data"] = data
self.render_page('ajax/notify.html', page_data) self.render_page("ajax/notify.html", page_data)
elif page == "get_zip_tree": elif page == "get_zip_tree":
path = self.get_argument('path', None) path = self.get_argument("path", None)
self.write(helper.get_os_understandable_path(path) + '\n' + self.write(
helper.generate_zip_tree(path)) Helpers.get_os_understandable_path(path)
+ "\n"
+ Helpers.generate_zip_tree(path)
)
self.finish() self.finish()
elif page == "get_zip_dir": elif page == "get_zip_dir":
path = self.get_argument('path', None) path = self.get_argument("path", None)
self.write(helper.get_os_understandable_path(path) + '\n' + self.write(
helper.generate_zip_dir(path)) Helpers.get_os_understandable_path(path)
+ "\n"
+ Helpers.generate_zip_dir(path)
)
self.finish() self.finish()
elif page == "get_backup_tree": elif page == "get_backup_tree":
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
folder = self.get_argument('path', None) folder = self.get_argument("path", None)
output = "" output = ""
@ -119,31 +118,31 @@ class AjaxHandler(BaseHandler):
dir_list.append(item) dir_list.append(item)
else: else:
unsorted_files.append(item) unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold) file_list = sorted(dir_list, key=str.casefold) + sorted(
output += \ unsorted_files, key=str.casefold
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\ )
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
for raw_filename in file_list: for raw_filename in file_list:
filename = html.escape(raw_filename) filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename) rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename) dpath = os.path.join(folder, filename)
if str(dpath) in self.controller.management.get_excluded_backup_dirs(server_id): if str(dpath) in self.controller.management.get_excluded_backup_dirs(
server_id
):
if os.path.isdir(rel): if os.path.isdir(rel):
output += \ output += f"""<li class="tree-item" data-path="{dpath}">
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"> \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> <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)"> <span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i class="far fa-folder"></i> <i style="color: #8862e0;" class="far fa-folder"></i>
<i class="far fa-folder-open"></i> <i style="color: #8862e0;" class="far fa-folder-open"></i>
<strong>{filename}</strong> <strong>{filename}</strong>
</span> </span>
</input></div><li> </input></div><li>
\n"""\ \n"""
else: else:
output += f"""<li output += f"""<li
class="tree-nested d-block tree-ctx-item tree-file" class="d-block tree-ctx-item tree-file"
data-path="{dpath}" data-path="{dpath}"
data-name="{filename}" data-name="{filename}"
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}" checked><span style="margin-right: 6px;"> onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}" checked><span style="margin-right: 6px;">
@ -151,32 +150,30 @@ class AjaxHandler(BaseHandler):
else: else:
if os.path.isdir(rel): if os.path.isdir(rel):
output += \ output += f"""<li class="tree-item" data-path="{dpath}">
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"> \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}"> <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)"> <span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i class="far fa-folder"></i> <i style="color: #8862e0;" class="far fa-folder"></i>
<i class="far fa-folder-open"></i> <i style="color: #8862e0;" class="far fa-folder-open"></i>
<strong>{filename}</strong> <strong>{filename}</strong>
</span> </span>
</input></div><li> </input></div><li>
\n"""\ \n"""
else: else:
output += f"""<li output += f"""<li
class="tree-nested d-block tree-ctx-item tree-file" class="d-block tree-ctx-item tree-file"
data-path="{dpath}" data-path="{dpath}"
data-name="{filename}" data-name="{filename}"
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}"> 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>""" <span style="margin-right: 6px;"><i class="far fa-file">
self.write(helper.get_os_understandable_path(folder) + '\n' + </i></span></input>{filename}</li>"""
output) self.write(Helpers.get_os_understandable_path(folder) + "\n" + output)
self.finish() self.finish()
elif page == "get_backup_dir": elif page == "get_backup_dir":
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
folder = self.get_argument('path', None) folder = self.get_argument("path", None)
output = "" output = ""
dir_list = [] dir_list = []
@ -187,39 +184,38 @@ class AjaxHandler(BaseHandler):
dir_list.append(item) dir_list.append(item)
else: else:
unsorted_files.append(item) unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold) file_list = sorted(dir_list, key=str.casefold) + sorted(
output += \ unsorted_files, key=str.casefold
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\ )
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
for raw_filename in file_list: for raw_filename in file_list:
filename = html.escape(raw_filename) filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename) rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename) dpath = os.path.join(folder, filename)
if str(dpath) in self.controller.management.get_excluded_backup_dirs(server_id): if str(dpath) in self.controller.management.get_excluded_backup_dirs(
server_id
):
if os.path.isdir(rel): if os.path.isdir(rel):
output += \ output += f"""<li class="tree-item" data-path="{dpath}">
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"> \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}"> <input type="checkbox" name="root_path" value="{dpath}" checked>
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)"> <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"></i>
<i class="far fa-folder-open"></i> <i class="far fa-folder-open"></i>
<strong>{filename}</strong> <strong>{filename}</strong>
</span> </span>
</input></div><li>"""\ </input></div><li>"""
else: else:
output += f"""<li output += f"""<li
class="tree-item tree-nested d-block tree-ctx-item tree-file" class="tree-item tree-nested d-block tree-ctx-item tree-file"
data-path="{dpath}" data-path="{dpath}"
data-name="{filename}" data-name="{filename}"
onclick=""><input type='checkbox' name='root_path' value='{dpath}'><span style="margin-right: 6px;"> onclick=""><input type='checkbox' name='root_path' value='{dpath}' checked><span style="margin-right: 6px;">
<i class="far fa-file"></i></span></input>{filename}</li>""" <i class="far fa-file"></i></span></input>{filename}</li>"""
else: else:
if os.path.isdir(rel): if os.path.isdir(rel):
output += \ output += f"""<li class="tree-item" data-path="{dpath}">
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"> \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}"> <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)"> <span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
@ -227,272 +223,361 @@ class AjaxHandler(BaseHandler):
<i class="far fa-folder-open"></i> <i class="far fa-folder-open"></i>
<strong>{filename}</strong> <strong>{filename}</strong>
</span> </span>
</input></div><li>"""\ </input></div><li>"""
else: else:
output += f"""<li output += f"""<li
class="tree-item tree-nested d-block tree-ctx-item tree-file" class="tree-item tree-nested d-block tree-ctx-item tree-file"
data-path="{dpath}" data-path="{dpath}"
data-name="{filename}" data-name="{filename}"
onclick=""><input type='checkbox' name='root_path' value='{dpath}'> onclick=""><input type='checkbox' name='root_path' value='{dpath}'>
<span style="margin-right: 6px;"><i class="far fa-file"></i></span></input>{filename}</li>""" <span style="margin-right: 6px;"><i class="far fa-file">
</i></span></input>{filename}</li>"""
self.write(helper.get_os_understandable_path(folder) + '\n' + self.write(Helpers.get_os_understandable_path(folder) + "\n" + output)
output)
self.finish() self.finish()
elif page == "get_dir": elif page == "get_dir":
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
path = self.get_argument('path', None) path = self.get_argument("path", None)
if not self.check_server_id(server_id, 'get_tree'): if not self.check_server_id(server_id, "get_tree"):
return return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path): if Helpers.validate_traversal(
self.write(helper.get_os_understandable_path(path) + '\n' + self.controller.servers.get_server_data_by_id(server_id)["path"], path
helper.generate_dir(path)) ):
self.write(
Helpers.get_os_understandable_path(path)
+ "\n"
+ Helpers.generate_dir(path)
)
self.finish() self.finish()
@tornado.web.authenticated @tornado.web.authenticated
def post(self, page): def post(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
superuser = exec_user['superuser'] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
permissions = { permissions = {
'Commands': Enum_Permissions_Server.Commands, "Commands": EnumPermissionsServer.COMMANDS,
'Terminal': Enum_Permissions_Server.Terminal, "Terminal": EnumPermissionsServer.TERMINAL,
'Logs': Enum_Permissions_Server.Logs, "Logs": EnumPermissionsServer.LOGS,
'Schedule': Enum_Permissions_Server.Schedule, "Schedule": EnumPermissionsServer.SCHEDULE,
'Backup': Enum_Permissions_Server.Backup, "Backup": EnumPermissionsServer.BACKUP,
'Files': Enum_Permissions_Server.Files, "Files": EnumPermissionsServer.FILES,
'Config': Enum_Permissions_Server.Config, "Config": EnumPermissionsServer.CONFIG,
'Players': Enum_Permissions_Server.Players, "Players": EnumPermissionsServer.PLAYERS,
} }
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id) user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
if page == "send_command": if page == "send_command":
command = self.get_body_argument('command', default=None, strip=True) command = self.get_body_argument("command", default=None, strip=True)
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
if server_id is None: if server_id is None:
logger.warning("Server ID not found in send_command ajax call") logger.warning("Server ID not found in send_command ajax call")
console.warning("Server ID not found in send_command ajax call") Console.warning("Server ID not found in send_command ajax call")
srv_obj = self.controller.get_server_obj(server_id) srv_obj = self.controller.get_server_obj(server_id)
if command == srv_obj.settings['stop_command']: if command == srv_obj.settings["stop_command"]:
logger.info("Stop command detected as terminal input - intercepting." + logger.info(
f"Starting Crafty's stop process for server with id: {server_id}") "Stop command detected as terminal input - intercepting."
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), 'stop_server') + 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 command = None
elif command == 'restart': elif command == "restart":
logger.info("Restart command detected as terminal input - intercepting." + logger.info(
f"Starting Crafty's stop process for server with id: {server_id}") "Restart command detected as terminal input - intercepting."
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), 'restart_server') + 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 command = None
if command: if command:
if srv_obj.check_running(): if srv_obj.check_running():
srv_obj.send_command(command) srv_obj.send_command(command)
self.controller.management.add_to_audit_log(exec_user['user_id'], self.controller.management.add_to_audit_log(
f"Sent command to {self.controller.servers.get_server_friendly_name(server_id)} terminal: {command}", exec_user["user_id"],
server_id, f"Sent command to "
self.get_remote_ip()) f"{self.controller.servers.get_server_friendly_name(server_id)} "
f"terminal: {command}",
server_id,
self.get_remote_ip(),
)
elif page == "send_order": elif page == "send_order":
self.controller.users.update_server_order(exec_user['user_id'], bleach.clean(self.get_argument('order'))) self.controller.users.update_server_order(
exec_user["user_id"], bleach.clean(self.get_argument("order"))
)
return return
elif page == "backup_now":
server_id = self.get_argument("id", None)
if server_id is None:
logger.error("Server ID is none. Canceling backup!")
return
server = self.controller.get_server_obj(server_id)
self.controller.management.add_to_audit_log_raw(
self.controller.users.get_user_by_id(exec_user["user_id"])["username"],
exec_user["user_id"],
server_id,
f"Backup now executed for server {server_id} ",
source_ip=self.get_remote_ip(),
)
server.backup_server()
elif page == "clear_comms": elif page == "clear_comms":
if exec_user['superuser']: if exec_user["superuser"]:
self.controller.clear_unexecuted_commands() self.controller.clear_unexecuted_commands()
return return
elif page == "kill": elif page == "kill":
if not permissions['Commands'] in user_perms: if not permissions["Commands"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Commands") self.redirect("/panel/error?error=Unauthorized access to Commands")
return return
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
svr = self.controller.get_server_obj(server_id) svr = self.controller.get_server_obj(server_id)
try: try:
svr.kill() svr.kill()
time.sleep(5)
svr.cleanup_server_object()
svr.record_server_stats()
except Exception as e: except Exception as e:
logger.error(f"Could not find PID for requested termsig. Full error: {e}") logger.error(
f"Could not find PID for requested termsig. Full error: {e}"
)
return return
elif page == "eula": elif page == "eula":
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
svr = self.controller.get_server_obj(server_id) svr = self.controller.get_server_obj(server_id)
svr.agree_eula(exec_user['user_id']) svr.agree_eula(exec_user["user_id"])
elif page == "restore_backup": elif page == "restore_backup":
if not permissions['Backup'] in user_perms: if not permissions["Backup"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Backups") self.redirect("/panel/error?error=Unauthorized access to Backups")
return return
server_id = bleach.clean(self.get_argument('id', None)) server_id = bleach.clean(self.get_argument("id", None))
zip_name = bleach.clean(self.get_argument('zip_file', None)) zip_name = bleach.clean(self.get_argument("zip_file", None))
svr_obj = self.controller.servers.get_server_obj(server_id) svr_obj = self.controller.servers.get_server_obj(server_id)
server_data = self.controller.servers.get_server_data_by_id(server_id) server_data = self.controller.servers.get_server_data_by_id(server_id)
if server_data['type'] == 'minecraft-java': if server_data["type"] == "minecraft-java":
backup_path = svr_obj.backup_path backup_path = svr_obj.backup_path
if helper.validate_traversal(backup_path, zip_name): if Helpers.validate_traversal(backup_path, zip_name):
tempDir = helper.unzip_backup_archive(backup_path, zip_name) temp_dir = Helpers.unzip_backup_archive(backup_path, zip_name)
new_server = self.controller.import_zip_server(svr_obj.server_name, new_server = self.controller.import_zip_server(
tempDir, svr_obj.server_name,
server_data['executable'], temp_dir,
'1', '2', server_data["executable"],
server_data['server_port']) "1",
"2",
server_data["server_port"],
)
new_server_id = new_server new_server_id = new_server
new_server = self.controller.get_server_data(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.rename_backup_dir(
server_id, new_server_id, new_server["server_uuid"]
)
self.controller.remove_server(server_id, True) self.controller.remove_server(server_id, True)
self.redirect('/panel/dashboard') self.redirect("/panel/dashboard")
else: else:
backup_path = svr_obj.backup_path backup_path = svr_obj.backup_path
if helper.validate_traversal(backup_path, zip_name): if Helpers.validate_traversal(backup_path, zip_name):
tempDir = helper.unzip_backup_archive(backup_path, zip_name) temp_dir = Helpers.unzip_backup_archive(backup_path, zip_name)
new_server = self.controller.import_bedrock_zip_server(svr_obj.server_name, new_server = self.controller.import_bedrock_zip_server(
tempDir, svr_obj.server_name,
server_data['executable'], temp_dir,
server_data['server_port']) server_data["executable"],
server_data["server_port"],
)
new_server_id = new_server new_server_id = new_server
new_server = self.controller.get_server_data(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.rename_backup_dir(
server_id, new_server_id, new_server["server_uuid"]
)
self.controller.remove_server(server_id, True) self.controller.remove_server(server_id, True)
self.redirect('/panel/dashboard') self.redirect("/panel/dashboard")
elif page == "unzip_server": elif page == "unzip_server":
path = self.get_argument('path', None) path = self.get_argument("path", None)
if helper.check_file_exists(path): if Helpers.check_file_exists(path):
helper.unzipServer(path, exec_user['user_id']) self.helper.unzip_server(path, exec_user["user_id"])
else: else:
user_id = exec_user['user_id'] user_id = exec_user["user_id"]
if user_id: if user_id:
time.sleep(5) time.sleep(5)
user_lang = self.controller.users.get_user_lang_by_id(user_id) user_lang = self.controller.users.get_user_lang_by_id(user_id)
websocket_helper.broadcast_user(user_id, 'send_start_error',{ self.helper.websocket_helper.broadcast_user(
'error': translation.translate('error', 'no-file', user_lang) user_id,
}) "send_start_error",
{
"error": self.helper.translation.translate(
"error", "no-file", user_lang
)
},
)
return return
elif page == "backup_select": elif page == "backup_select":
path = self.get_argument('path', None) path = self.get_argument("path", None)
helper.backup_select(path, exec_user['user_id']) self.helper.backup_select(path, exec_user["user_id"])
return return
@tornado.web.authenticated @tornado.web.authenticated
def delete(self, page): def delete(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
superuser = exec_user['superuser'] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
permissions = { permissions = {
'Commands': Enum_Permissions_Server.Commands, "Commands": EnumPermissionsServer.COMMANDS,
'Terminal': Enum_Permissions_Server.Terminal, "Terminal": EnumPermissionsServer.TERMINAL,
'Logs': Enum_Permissions_Server.Logs, "Logs": EnumPermissionsServer.LOGS,
'Schedule': Enum_Permissions_Server.Schedule, "Schedule": EnumPermissionsServer.SCHEDULE,
'Backup': Enum_Permissions_Server.Backup, "Backup": EnumPermissionsServer.BACKUP,
'Files': Enum_Permissions_Server.Files, "Files": EnumPermissionsServer.FILES,
'Config': Enum_Permissions_Server.Config, "Config": EnumPermissionsServer.CONFIG,
'Players': Enum_Permissions_Server.Players, "Players": EnumPermissionsServer.PLAYERS,
} }
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id) user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
if page == "del_task": if page == "del_task":
if not permissions['Schedule'] in user_perms: if not permissions["Schedule"] in user_perms:
self.redirect("/panel/error?error=Unauthorized access to Tasks") self.redirect("/panel/error?error=Unauthorized access to Tasks")
else: else:
sch_id = self.get_argument('schedule_id', '-404') sch_id = self.get_argument("schedule_id", "-404")
self.tasks_manager.remove_job(sch_id) self.tasks_manager.remove_job(sch_id)
if page == "del_backup": if page == "del_backup":
if not permissions['Backup'] in user_perms: if not permissions["Backup"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Backups") self.redirect("/panel/error?error=Unauthorized access to Backups")
return return
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True)) file_path = Helpers.get_os_understandable_path(
server_id = self.get_argument('id', None) self.get_body_argument("file_path", default=None, strip=True)
)
server_id = self.get_argument("id", None)
console.warning(f"Delete {file_path} for server {server_id}") Console.warning(f"Delete {file_path} for server {server_id}")
if not self.check_server_id(server_id, 'del_backup'): if not self.check_server_id(server_id, "del_backup"):
return return
else: server_id = bleach.clean(server_id) else:
server_id = bleach.clean(server_id)
server_info = self.controller.servers.get_server_data_by_id(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id)
if not (helper.in_path(helper.get_os_understandable_path(server_info['path']), file_path) \ if not (
or helper.in_path(helper.get_os_understandable_path(server_info['backup_path']), file_path)) \ Helpers.in_path(
or not helper.check_file_exists(os.path.abspath(file_path)): Helpers.get_os_understandable_path(server_info["path"]), file_path
)
or Helpers.in_path(
Helpers.get_os_understandable_path(server_info["backup_path"]),
file_path,
)
) or not Helpers.check_file_exists(os.path.abspath(file_path)):
logger.warning(f"Invalid path in del_backup ajax call ({file_path})") logger.warning(f"Invalid path in del_backup ajax call ({file_path})")
console.warning(f"Invalid path in del_backup ajax call ({file_path})") Console.warning(f"Invalid path in del_backup ajax call ({file_path})")
return return
# Delete the file # Delete the file
if helper.validate_traversal(helper.get_os_understandable_path(server_info['backup_path']), file_path): if Helpers.validate_traversal(
Helpers.get_os_understandable_path(server_info["backup_path"]),
file_path,
):
os.remove(file_path) os.remove(file_path)
elif page == "delete_server": elif page == "delete_server":
if not permissions['Config'] in user_perms: if not permissions["Config"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config") self.redirect("/panel/error?error=Unauthorized access to Config")
return return
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
logger.info(f"Removing server from panel for server: {self.controller.servers.get_server_friendly_name(server_id)}") logger.info(
f"Removing server from panel for server: "
f"{self.controller.servers.get_server_friendly_name(server_id)}"
)
server_data = self.controller.get_server_data(server_id) server_data = self.controller.get_server_data(server_id)
server_name = server_data['server_name'] server_name = server_data["server_name"]
self.controller.management.add_to_audit_log(exec_user['user_id'], self.controller.management.add_to_audit_log(
f"Deleted server {server_id} named {server_name}", exec_user["user_id"],
server_id, f"Deleted server {server_id} named {server_name}",
self.get_remote_ip()) server_id,
self.get_remote_ip(),
)
self.tasks_manager.remove_all_server_tasks(server_id) self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, False) self.controller.remove_server(server_id, False)
elif page == "delete_server_files": elif page == "delete_server_files":
if not permissions['Config'] in user_perms: if not permissions["Config"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config") self.redirect("/panel/error?error=Unauthorized access to Config")
return return
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
logger.info(f"Removing server and all associated files for server: {self.controller.servers.get_server_friendly_name(server_id)}") logger.info(
f"Removing server and all associated files for server: "
f"{self.controller.servers.get_server_friendly_name(server_id)}"
)
server_data = self.controller.get_server_data(server_id) server_data = self.controller.get_server_data(server_id)
server_name = server_data['server_name'] server_name = server_data["server_name"]
self.controller.management.add_to_audit_log(exec_user['user_id'], self.controller.management.add_to_audit_log(
f"Deleted server {server_id} named {server_name}", exec_user["user_id"],
server_id, f"Deleted server {server_id} named {server_name}",
self.get_remote_ip()) server_id,
self.get_remote_ip(),
)
self.tasks_manager.remove_all_server_tasks(server_id) self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, True) self.controller.remove_server(server_id, True)
def check_server_id(self, server_id, page_name): def check_server_id(self, server_id, page_name):
if server_id is None: if server_id is None:
logger.warning(f"Server ID not defined in {page_name} ajax call ({server_id})") logger.warning(
console.warning(f"Server ID not defined in {page_name} ajax call ({server_id})") 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 return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
# does this server id exist? # does this server id exist?
if not self.controller.servers.server_id_exists(server_id): if not self.controller.servers.server_id_exists(server_id):
logger.warning(f"Server ID not found in {page_name} ajax call ({server_id})") logger.warning(
console.warning(f"Server ID not found in {page_name} ajax call ({server_id})") 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
return True return True

View File

@ -1,55 +1,93 @@
from datetime import datetime
import logging import logging
import re import re
from app.classes.controllers.crafty_perms_controller import EnumPermissionsCrafty
from app.classes.controllers.server_perms_controller import EnumPermissionsServer
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
bearer_pattern = re.compile(r'^Bearer', flags=re.IGNORECASE) bearer_pattern = re.compile(r"^Bearer", flags=re.IGNORECASE)
class ApiHandler(BaseHandler): class ApiHandler(BaseHandler):
def return_response(self, status: int, data: dict): def return_response(self, status: int, data: dict):
# Define a standardized response # Define a standardized response
self.set_status(status) self.set_status(status)
self.write(data) self.write(data)
def access_denied(self, user, reason=''): def check_xsrf_cookie(self):
# Disable CSRF protection on API routes
pass
def access_denied(self, user, reason=""):
if reason: if reason:
reason = ' because ' + 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()) logger.info(
self.finish(self.return_response(403, { "User %s from IP %s was denied access to the API route "
'error':'ACCESS_DENIED', + self.request.path
'info':'You were denied access to the requested resource' + 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: def authenticate_user(self) -> bool:
self.permissions = {
"Commands": EnumPermissionsServer.COMMANDS,
"Terminal": EnumPermissionsServer.TERMINAL,
"Logs": EnumPermissionsServer.LOGS,
"Schedule": EnumPermissionsServer.SCHEDULE,
"Backup": EnumPermissionsServer.BACKUP,
"Files": EnumPermissionsServer.FILES,
"Config": EnumPermissionsServer.CONFIG,
"Players": EnumPermissionsServer.PLAYERS,
"Server_Creation": EnumPermissionsCrafty.SERVER_CREATION,
"User_Config": EnumPermissionsCrafty.USER_CONFIG,
"Roles_Config": EnumPermissionsCrafty.ROLES_CONFIG,
}
try: try:
logger.debug("Searching for specified token") logger.debug("Searching for specified token")
api_token = self.get_argument('token', '') api_token = self.get_argument("token", "")
if api_token is None and self.request.headers.get('Authorization'): self.api_token = api_token
api_token = bearer_pattern.sub('', self.request.headers.get('Authorization')) 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: elif api_token is None:
api_token = self.get_cookie('token') api_token = self.get_cookie("token")
user_data = self.controller.users.get_user_by_api_token(api_token) user_data = self.controller.users.get_user_by_api_token(api_token)
logger.debug("Checking results") logger.debug("Checking results")
if user_data: if user_data:
# Login successful! Check perms # Login successful! Check perms
logger.info(f"User {user_data['username']} has authenticated to API") logger.info(f"User {user_data['username']} has authenticated to API")
# TODO: Role check
return True # This is to set the "authenticated" return True # This is to set the "authenticated"
else: else:
logging.debug("Auth unsuccessful") logging.debug("Auth unsuccessful")
self.access_denied("unknown", "the user provided an invalid token") self.access_denied("unknown", "the user provided an invalid token")
return False return False
except Exception as e: except Exception as e:
logger.warning("An error occured while authenticating an API user: %s", e) logger.warning("An error occured while authenticating an API user: %s", e)
self.finish(self.return_response(403, { self.finish(
'error':'ACCESS_DENIED', self.return_response(
'info':'An error occured while authenticating the user' 403,
})) {
"error": "ACCESS_DENIED",
"info": "An error occured while authenticating the user",
},
)
)
return False return False
@ -57,12 +95,28 @@ class ServersStats(ApiHandler):
def get(self): def get(self):
"""Get details about all servers""" """Get details about all servers"""
authenticated = self.authenticate_user() authenticated = self.authenticate_user()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
if not authenticated: if not authenticated:
return return
if user_obj["superuser"]:
raw_stats = self.controller.servers.get_all_servers_stats()
else:
raw_stats = self.controller.servers.get_authorized_servers_stats(
user_obj["user_id"]
)
stats = []
for rs in raw_stats:
s = {}
for k, v in rs["server_data"].items():
if isinstance(v, datetime):
s[k] = v.timestamp()
else:
s[k] = v
stats.append(s)
# Get server stats # Get server stats
# TODO Check perms # TODO Check perms
self.finish(self.write({"servers": self.controller.stats.get_servers_stats()})) self.finish(self.write({"servers": stats}))
class NodeStats(ApiHandler): class NodeStats(ApiHandler):
@ -74,5 +128,311 @@ class NodeStats(ApiHandler):
# Get node stats # Get node stats
node_stats = self.controller.stats.get_node_stats() node_stats = self.controller.stats.get_node_stats()
node_stats.pop("servers") self.return_response(200, {"code": node_stats["node_stats"]})
self.finish(self.write(node_stats))
class SendCommand(ApiHandler):
def post(self):
user = self.authenticate_user()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
if user is None:
self.access_denied("unknown")
return
server_id = self.get_argument("id")
if (
not user_obj["user_id"]
in self.controller.server_perms.get_server_user_list(server_id)
and not user_obj["superuser"]
):
self.access_denied("unknown")
return
if not self.permissions[
"Commands"
] in self.controller.server_perms.get_api_key_permissions_list(
self.controller.users.get_api_key_by_token(self.api_token), server_id
):
self.access_denied(user)
return
command = self.get_argument("command", default=None, strip=True)
server_id = self.get_argument("id")
if command:
server = self.controller.get_server_obj(server_id)
if server.check_running:
server.send_command(command)
self.return_response(200, {"run": True})
else:
self.return_response(200, {"error": "SER_NOT_RUNNING"})
else:
self.return_response(200, {"error": "NO_COMMAND"})
class ServerBackup(ApiHandler):
def post(self):
user = self.authenticate_user()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
if user is None:
self.access_denied("unknown")
return
server_id = self.get_argument("id")
if (
not user_obj["user_id"]
in self.controller.server_perms.get_server_user_list(server_id)
and not user_obj["superuser"]
):
self.access_denied("unknown")
return
if not self.permissions[
"Backup"
] in self.controller.server_perms.get_api_key_permissions_list(
self.controller.users.get_api_key_by_token(self.api_token), server_id
):
self.access_denied(user)
return
server = self.controller.get_server_obj(server_id)
server.backup_server()
self.return_response(200, {"code": "SER_BAK_CALLED"})
class StartServer(ApiHandler):
def post(self):
user = self.authenticate_user()
remote_ip = self.get_remote_ip()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
if user is None:
self.access_denied("unknown")
return
server_id = self.get_argument("id")
if (
not user_obj["user_id"]
in self.controller.server_perms.get_server_user_list(server_id)
and not user_obj["superuser"]
):
self.access_denied("unknown")
return
elif not self.permissions[
"Commands"
] in self.controller.server_perms.get_api_key_permissions_list(
self.controller.users.get_api_key_by_token(self.api_token), server_id
):
self.access_denied("unknown")
return
server = self.controller.get_server_obj(server_id)
if not server.check_running():
self.controller.management.send_command(
user_obj["user_id"], server_id, remote_ip, "start_server"
)
self.return_response(200, {"code": "SER_START_CALLED"})
else:
self.return_response(500, {"error": "SER_RUNNING"})
class StopServer(ApiHandler):
def post(self):
user = self.authenticate_user()
remote_ip = self.get_remote_ip()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
if user is None:
self.access_denied("unknown")
return
server_id = self.get_argument("id")
if (
not user_obj["user_id"]
in self.controller.server_perms.get_server_user_list(server_id)
and not user_obj["superuser"]
):
self.access_denied("unknown")
if not self.permissions[
"Commands"
] in self.controller.server_perms.get_api_key_permissions_list(
self.controller.users.get_api_key_by_token(self.api_token), server_id
):
self.access_denied(user)
return
server = self.controller.get_server_obj(server_id)
if server.check_running():
self.controller.management.send_command(
user, server_id, remote_ip, "stop_server"
)
self.return_response(200, {"code": "SER_STOP_CALLED"})
else:
self.return_response(500, {"error": "SER_NOT_RUNNING"})
class RestartServer(ApiHandler):
def post(self):
user = self.authenticate_user()
remote_ip = self.get_remote_ip()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
if user is None:
self.access_denied("unknown")
return
server_id = self.get_argument("id")
if not user_obj["user_id"] in self.controller.server_perms.get_server_user_list(
server_id
):
self.access_denied("unknown")
if not self.permissions[
"Commands"
] in self.controller.server_perms.get_api_key_permissions_list(
self.controller.users.get_api_key_by_token(self.api_token), server_id
):
self.access_denied(user)
self.controller.management.send_command(
user, server_id, remote_ip, "restart_server"
)
self.return_response(200, {"code": "SER_RESTART_CALLED"})
class CreateUser(ApiHandler):
def post(self):
user = self.authenticate_user()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
user_perms = self.controller.crafty_perms.get_crafty_permissions_list(
user_obj["user_id"]
)
if (
not self.permissions["User_Config"] in user_perms
and not user_obj["superuser"]
):
self.access_denied("unknown")
return
if user is None:
self.access_denied("unknown")
return
if not self.permissions[
"User_Config"
] in self.controller.crafty_perms.get_api_key_permissions_list(
self.controller.users.get_api_key_by_token(self.api_token)
):
self.access_denied(user)
return
new_username = self.get_argument("username")
new_pass = self.get_argument("password")
if new_username:
self.controller.users.add_user(
new_username, new_pass, "default@example.com", True, False
)
self.return_response(
200,
{
"code": "COMPLETE",
"username": new_username,
"password": new_pass,
},
)
else:
self.return_response(
500,
{
"error": "MISSING_PARAMS",
"info": "Some paramaters failed validation",
},
)
class DeleteUser(ApiHandler):
def post(self):
user = self.authenticate_user()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
user_perms = self.controller.crafty_perms.get_crafty_permissions_list(
user_obj["user_id"]
)
if (
not self.permissions["User_Config"] in user_perms
and not user_obj["superuser"]
):
self.access_denied("unknown")
return
if user is None:
self.access_denied("unknown")
return
if not self.permissions[
"User_Config"
] in self.controller.crafty_perms.get_api_key_permissions_list(
self.controller.users.get_api_key_by_token(self.api_token)
):
self.access_denied(user)
return
user_id = self.get_argument("user_id", None, True)
user_to_del = self.controller.users.get_user_by_id(user_id)
if user_to_del["superuser"]:
self.return_response(
500,
{"error": "NOT_ALLOWED", "info": "You cannot delete a super user"},
)
else:
if user_id:
self.controller.users.remove_user(user_id)
self.return_response(200, {"code": "COMPLETED"})
class ListServers(ApiHandler):
def get(self):
user = self.authenticate_user()
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
if user is None:
self.access_denied("unknown")
return
if self.api_token is None:
self.access_denied("unknown")
return
if user_obj["superuser"]:
servers = self.controller.servers.get_all_defined_servers()
servers = [str(i) for i in servers]
else:
servers = self.controller.servers.get_authorized_servers(
user_obj["user_id"]
)
servers = [str(i) for i in servers]
self.return_response(
200,
{
"code": "COMPLETED",
"servers": servers,
},
)

View File

@ -0,0 +1,23 @@
from typing import Awaitable, Callable, Optional
from app.classes.web.base_handler import BaseHandler
class BaseApiHandler(BaseHandler):
# {{{ Disable XSRF protection on API routes
def check_xsrf_cookie(self) -> None:
pass
# }}}
# {{{ 405 Method Not Allowed as JSON
def _unimplemented_method(self, *_args: str, **_kwargs: str) -> None:
self.finish_json(405, {"status": "error", "error": "METHOD_NOT_ALLOWED"})
head = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
get = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
post = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
delete = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
patch = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
put = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
options = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
# }}}

View File

@ -1,44 +1,87 @@
import logging import logging
from typing import ( import re
Union, import typing as t
List, import orjson
Optional, Tuple, Dict, Any import bleach
) import tornado.web
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.users import ApiKeys from app.classes.models.users import ApiKeys
from app.classes.shared.authentication import authentication from app.classes.shared.helpers import Helpers
from app.classes.shared.main_controller import Controller from app.classes.shared.main_controller import Controller
from app.classes.shared.helpers import helper from app.classes.shared.translation import Translation
try:
import tornado.web
import bleach
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
bearer_pattern = re.compile(r"^Bearer ", flags=re.IGNORECASE)
class BaseHandler(tornado.web.RequestHandler): class BaseHandler(tornado.web.RequestHandler):
def set_default_headers(self) -> None:
"""
Fix CORS
"""
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header(
"Access-Control-Allow-Headers",
"Content-Type, x-requested-with, Authorization",
)
self.set_header(
"Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS"
)
def options(self, *_, **__):
"""
Fix CORS
"""
# no body
self.set_status(204)
self.finish()
nobleach = {bool, type(None)} nobleach = {bool, type(None)}
redactables = ("pass", "api") redactables = ("pass", "api")
helper: Helpers
controller: Controller
translator: Translation
# noinspection PyAttributeOutsideInit # noinspection PyAttributeOutsideInit
def initialize(self, controller: Controller = None, tasks_manager=None, translator=None): def initialize(
self, helper=None, controller=None, tasks_manager=None, translator=None
):
self.helper = helper
self.controller = controller self.controller = controller
self.tasks_manager = tasks_manager self.tasks_manager = tasks_manager
self.translator = translator self.translator = translator
def get_remote_ip(self): def get_remote_ip(self):
remote_ip = self.request.headers.get("X-Real-IP") or \ remote_ip = (
self.request.headers.get("X-Forwarded-For") or \ self.request.headers.get("X-Real-IP")
self.request.remote_ip or self.request.headers.get("X-Forwarded-For")
or self.request.remote_ip
)
return remote_ip return remote_ip
current_user: Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]] current_user: t.Tuple[t.Optional[ApiKeys], t.Dict[str, t.Any], t.Dict[str, t.Any]]
def get_current_user(self) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]: """
return authentication.check(self.get_cookie("token")) A variable that contains the current user's data. Please see
Please only use this with routes using the `@tornado.web.authenticated` decorator.
"""
def get_current_user(
self,
) -> t.Optional[
t.Tuple[t.Optional[ApiKeys], t.Dict[str, t.Any], t.Dict[str, t.Any]]
]:
"""
Get the token's API key, the token's payload and user data.
Returns:
t.Optional[ApiKeys]: The API key of the token.
t.Dict[str, t.Any]: The token's payload.
t.Dict[str, t.Any]: The user's data from the database.
"""
return self.controller.authentication.check(self.get_cookie("token"))
def autobleach(self, name, text): def autobleach(self, name, text):
for r in self.redactables: for r in self.redactables:
@ -54,15 +97,17 @@ class BaseHandler(tornado.web.RequestHandler):
return bleach.clean(text) return bleach.clean(text)
def get_argument( def get_argument(
self, self,
name: str, name: str,
default: Union[None, str, tornado.web._ArgDefaultMarker] = tornado.web._ARG_DEFAULT, default: t.Union[
strip: bool = True, None, str, tornado.web._ArgDefaultMarker
) -> Optional[str]: ] = tornado.web._ARG_DEFAULT,
strip: bool = True,
) -> t.Optional[str]:
arg = self._get_argument(name, default, self.request.arguments, strip) arg = self._get_argument(name, default, self.request.arguments, strip)
return self.autobleach(name, arg) return self.autobleach(name, arg)
def get_arguments(self, name: str, strip: bool = True) -> List[str]: def get_arguments(self, name: str, strip: bool = True) -> t.List[str]:
if not isinstance(strip, bool): if not isinstance(strip, bool):
raise AssertionError raise AssertionError
args = self._get_arguments(name, self.request.arguments, strip) args = self._get_arguments(name, self.request.arguments, strip)
@ -70,3 +115,127 @@ class BaseHandler(tornado.web.RequestHandler):
for arg in args: for arg in args:
args_ret += self.autobleach(name, arg) args_ret += self.autobleach(name, arg)
return args_ret return args_ret
def access_denied(self, user: t.Optional[str], reason: t.Optional[str]):
ip = self.get_remote_ip()
route = self.request.path
if user is not None:
user_data = f"User {user} from IP {ip}"
else:
user_data = f"An unknown user from IP {ip}"
if reason:
ending = f"to the API route {route} because {reason}"
else:
ending = f"to the API route {route}"
logger.info(f"{user_data} was denied access {ending}")
self.finish_json(
403,
{
"status": "error",
"error": "ACCESS_DENIED",
"info": "You were denied access to the requested resource",
},
)
def _auth_get_api_token(self) -> t.Optional[str]:
"""Get an API token from the request
The API token is searched in the following order:
1. The `token` query parameter
2. The `Authorization` header
3. The `token` cookie
Returns:
t.Optional[str]: The API token or None if no token was found.
"""
logger.debug("Searching for specified token")
api_token = self.get_query_argument("token", None)
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")
return api_token
def authenticate_user(
self,
) -> t.Optional[
t.Tuple[
t.List,
t.List[EnumPermissionsCrafty],
t.List[str],
bool,
t.Dict[str, t.Any],
]
]:
try:
api_key, _token_data, user = self.controller.authentication.check_err(
self._auth_get_api_token()
)
superuser = user["superuser"]
if api_key is not None:
superuser = superuser and api_key.superuser
exec_user_role = set()
if superuser:
authorized_servers = self.controller.list_defined_servers()
exec_user_role.add("Super User")
exec_user_crafty_permissions = (
self.controller.crafty_perms.list_defined_crafty_permissions()
)
else:
if api_key is not None:
exec_user_crafty_permissions = (
self.controller.crafty_perms.get_api_key_permissions_list(
api_key
)
)
else:
exec_user_crafty_permissions = (
self.controller.crafty_perms.get_crafty_permissions_list(
user["user_id"]
)
)
logger.debug(user["roles"])
for r in user["roles"]:
role = self.controller.roles.get_role(r)
exec_user_role.add(role["role_name"])
authorized_servers = self.controller.servers.get_authorized_servers(
user["user_id"] # TODO: API key authorized servers?
)
logger.debug("Checking results")
if user:
return (
authorized_servers,
exec_user_crafty_permissions,
exec_user_role,
superuser,
user,
)
else:
logging.debug("Auth unsuccessful")
self.access_denied(None, "the user provided an invalid token")
return None
except Exception as auth_exception:
logger.debug(
"An error occured while authenticating an API user:",
exc_info=auth_exception,
)
self.finish_json(
403,
{
"status": "error",
"error": "ACCESS_DENIED",
"info": "An error occured while authenticating the user",
},
)
return None
def finish_json(self, status: int, data: t.Dict[str, t.Any]):
self.set_status(status)
self.set_header("Content-Type", "application/json")
self.finish(orjson.dumps(data)) # pylint: disable=no-member

View File

@ -4,11 +4,11 @@ from app.classes.web.base_handler import BaseHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class DefaultHandler(BaseHandler): class DefaultHandler(BaseHandler):
# Override prepare() instead of get() to cover all possible HTTP methods. # Override prepare() instead of get() to cover all possible HTTP methods.
# pylint: disable=arguments-differ def prepare(self, page=None): # pylint: disable=arguments-differ
def prepare(self, page=None):
if page is not None: if page is not None:
self.set_status(404) self.set_status(404)
self.render( self.render(
@ -18,5 +18,5 @@ class DefaultHandler(BaseHandler):
else: else:
self.redirect( self.redirect(
"/public/login", "/public/login",
#translate=self.translator.translate, # translate=self.translator.translate,
) )

View File

@ -1,24 +1,19 @@
import os import os
import logging import logging
import bleach
import tornado.web
import tornado.escape
from app.classes.models.server_permissions import Enum_Permissions_Server from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.shared.console import console from app.classes.shared.console import Console
from app.classes.shared.helpers import helper from app.classes.shared.helpers import Helpers
from app.classes.shared.file_helpers import file_helper from app.classes.shared.file_helpers import FileHelpers
from app.classes.web.base_handler import BaseHandler 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__) logger = logging.getLogger(__name__)
class FileHandler(BaseHandler):
class FileHandler(BaseHandler):
def render_page(self, template, page_data): def render_page(self, template, page_data):
self.render( self.render(
template, template,
@ -29,370 +24,480 @@ class FileHandler(BaseHandler):
@tornado.web.authenticated @tornado.web.authenticated
def get(self, page): def get(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
superuser = exec_user['superuser'] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
permissions = { permissions = {
'Commands': Enum_Permissions_Server.Commands, "Commands": EnumPermissionsServer.COMMANDS,
'Terminal': Enum_Permissions_Server.Terminal, "Terminal": EnumPermissionsServer.TERMINAL,
'Logs': Enum_Permissions_Server.Logs, "Logs": EnumPermissionsServer.LOGS,
'Schedule': Enum_Permissions_Server.Schedule, "Schedule": EnumPermissionsServer.SCHEDULE,
'Backup': Enum_Permissions_Server.Backup, "Backup": EnumPermissionsServer.BACKUP,
'Files': Enum_Permissions_Server.Files, "Files": EnumPermissionsServer.FILES,
'Config': Enum_Permissions_Server.Config, "Config": EnumPermissionsServer.CONFIG,
'Players': Enum_Permissions_Server.Players, "Players": EnumPermissionsServer.PLAYERS,
} }
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id) user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
if page == "get_file": if page == "get_file":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
file_path = helper.get_os_understandable_path(self.get_argument('file_path', None)) file_path = Helpers.get_os_understandable_path(
self.get_argument("file_path", None)
)
if not self.check_server_id(server_id, 'get_file'): if not self.check_server_id(server_id, "get_file"):
return return
else: else:
server_id = bleach.clean(server_id) 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)\ if not Helpers.in_path(
or not helper.check_file_exists(os.path.abspath(file_path)): Helpers.get_os_understandable_path(
logger.warning(f"Invalid path in get_file file file ajax call ({file_path})") self.controller.servers.get_server_data_by_id(server_id)["path"]
console.warning(f"Invalid path in get_file file file ajax call ({file_path})") ),
file_path,
) or not Helpers.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 return
error = None error = None
try: try:
with open(file_path, encoding='utf-8') as file: with open(file_path, encoding="utf-8") as file:
file_contents = file.read() file_contents = file.read()
except UnicodeDecodeError: except UnicodeDecodeError:
file_contents = '' file_contents = ""
error = 'UnicodeDecodeError' error = "UnicodeDecodeError"
self.write({ self.write({"content": file_contents, "error": error})
'content': file_contents,
'error': error
})
self.finish() self.finish()
elif page == "get_tree": elif page == "get_tree":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
path = self.get_argument('path', None) path = self.get_argument("path", None)
if not self.check_server_id(server_id, 'get_tree'): if not self.check_server_id(server_id, "get_tree"):
return return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path): if Helpers.validate_traversal(
self.write(helper.get_os_understandable_path(path) + '\n' + self.controller.servers.get_server_data_by_id(server_id)["path"], path
helper.generate_tree(path)) ):
self.write(
Helpers.get_os_understandable_path(path)
+ "\n"
+ Helpers.generate_tree(path)
)
self.finish() self.finish()
elif page == "get_dir": elif page == "get_dir":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
path = self.get_argument('path', None) path = self.get_argument("path", None)
if not self.check_server_id(server_id, 'get_tree'): if not self.check_server_id(server_id, "get_tree"):
return return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path): if Helpers.validate_traversal(
self.write(helper.get_os_understandable_path(path) + '\n' + self.controller.servers.get_server_data_by_id(server_id)["path"], path
helper.generate_dir(path)) ):
self.write(
Helpers.get_os_understandable_path(path)
+ "\n"
+ Helpers.generate_dir(path)
)
self.finish() self.finish()
@tornado.web.authenticated @tornado.web.authenticated
def post(self, page): def post(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
superuser = exec_user['superuser'] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
permissions = { permissions = {
'Commands': Enum_Permissions_Server.Commands, "Commands": EnumPermissionsServer.COMMANDS,
'Terminal': Enum_Permissions_Server.Terminal, "Terminal": EnumPermissionsServer.TERMINAL,
'Logs': Enum_Permissions_Server.Logs, "Logs": EnumPermissionsServer.LOGS,
'Schedule': Enum_Permissions_Server.Schedule, "Schedule": EnumPermissionsServer.SCHEDULE,
'Backup': Enum_Permissions_Server.Backup, "Backup": EnumPermissionsServer.BACKUP,
'Files': Enum_Permissions_Server.Files, "Files": EnumPermissionsServer.FILES,
'Config': Enum_Permissions_Server.Config, "Config": EnumPermissionsServer.CONFIG,
'Players': Enum_Permissions_Server.Players, "Players": EnumPermissionsServer.PLAYERS,
} }
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id) user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
if page == "create_file": if page == "create_file":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
file_parent = helper.get_os_understandable_path(self.get_body_argument('file_parent', default=None, strip=True)) file_parent = Helpers.get_os_understandable_path(
file_name = self.get_body_argument('file_name', default=None, strip=True) 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) file_path = os.path.join(file_parent, file_name)
if not self.check_server_id(server_id, 'create_file'): if not self.check_server_id(server_id, "create_file"):
return return
else: else:
server_id = bleach.clean(server_id) 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) \ if not Helpers.in_path(
or helper.check_file_exists(os.path.abspath(file_path)): Helpers.get_os_understandable_path(
logger.warning(f"Invalid path in create_file file ajax call ({file_path})") self.controller.servers.get_server_data_by_id(server_id)["path"]
console.warning(f"Invalid path in create_file file ajax call ({file_path})") ),
file_path,
) or Helpers.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 return
# Create the file by opening it # Create the file by opening it
with open(file_path, 'w', encoding='utf-8') as file_object: with open(file_path, "w", encoding="utf-8") as file_object:
file_object.close() file_object.close()
elif page == "create_dir": elif page == "create_dir":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
dir_parent = helper.get_os_understandable_path(self.get_body_argument('dir_parent', default=None, strip=True)) dir_parent = Helpers.get_os_understandable_path(
dir_name = self.get_body_argument('dir_name', default=None, strip=True) 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) dir_path = os.path.join(dir_parent, dir_name)
if not self.check_server_id(server_id, 'create_dir'): if not self.check_server_id(server_id, "create_dir"):
return return
else: else:
server_id = bleach.clean(server_id) 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) \ if not Helpers.in_path(
or helper.check_path_exists(os.path.abspath(dir_path)): Helpers.get_os_understandable_path(
logger.warning(f"Invalid path in create_dir file ajax call ({dir_path})") self.controller.servers.get_server_data_by_id(server_id)["path"]
console.warning(f"Invalid path in create_dir file ajax call ({dir_path})") ),
dir_path,
) or Helpers.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 return
# Create the directory # Create the directory
os.mkdir(dir_path) os.mkdir(dir_path)
elif page == "unzip_file": elif page == "unzip_file":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
path = helper.get_os_understandable_path(self.get_argument('path', None)) path = Helpers.get_os_understandable_path(self.get_argument("path", None))
helper.unzipFile(path) Helpers.unzip_file(path)
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files") self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
return return
@tornado.web.authenticated @tornado.web.authenticated
def delete(self, page): def delete(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
superuser = exec_user['superuser'] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
permissions = { permissions = {
'Commands': Enum_Permissions_Server.Commands, "Commands": EnumPermissionsServer.COMMANDS,
'Terminal': Enum_Permissions_Server.Terminal, "Terminal": EnumPermissionsServer.TERMINAL,
'Logs': Enum_Permissions_Server.Logs, "Logs": EnumPermissionsServer.LOGS,
'Schedule': Enum_Permissions_Server.Schedule, "Schedule": EnumPermissionsServer.SCHEDULE,
'Backup': Enum_Permissions_Server.Backup, "Backup": EnumPermissionsServer.BACKUP,
'Files': Enum_Permissions_Server.Files, "Files": EnumPermissionsServer.FILES,
'Config': Enum_Permissions_Server.Config, "Config": EnumPermissionsServer.CONFIG,
'Players': Enum_Permissions_Server.Players, "Players": EnumPermissionsServer.PLAYERS,
} }
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id) user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
if page == "del_file": if page == "del_file":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True)) file_path = Helpers.get_os_understandable_path(
self.get_body_argument("file_path", default=None, strip=True)
)
console.warning(f"Delete {file_path} for server {server_id}") Console.warning(f"Delete {file_path} for server {server_id}")
if not self.check_server_id(server_id, 'del_file'): 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 return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
server_info = self.controller.servers.get_server_data_by_id(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id)
if not helper.in_path(helper.get_os_understandable_path(server_info['path']), dir_path) \ if not (
or not helper.check_path_exists(os.path.abspath(dir_path)): Helpers.in_path(
Helpers.get_os_understandable_path(server_info["path"]), file_path
)
or Helpers.in_path(
Helpers.get_os_understandable_path(server_info["backup_path"]),
file_path,
)
) or not Helpers.check_file_exists(os.path.abspath(file_path)):
logger.warning(f"Invalid path in del_file file ajax call ({file_path})")
Console.warning(
f"Invalid path in del_file file ajax call ({file_path})"
)
return
# Delete the file
FileHelpers.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 = Helpers.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 Helpers.in_path(
Helpers.get_os_understandable_path(server_info["path"]), dir_path
) or not Helpers.check_path_exists(os.path.abspath(dir_path)):
logger.warning(f"Invalid path in del_file file ajax call ({dir_path})") logger.warning(f"Invalid path in del_file file ajax call ({dir_path})")
console.warning(f"Invalid path in del_file file ajax call ({dir_path})") Console.warning(f"Invalid path in del_file file ajax call ({dir_path})")
return return
# Delete the directory # Delete the directory
# os.rmdir(dir_path) # Would only remove empty directories # os.rmdir(dir_path) # Would only remove empty directories
if helper.validate_traversal(helper.get_os_understandable_path(server_info['path']), dir_path): if Helpers.validate_traversal(
Helpers.get_os_understandable_path(server_info["path"]), dir_path
):
# Removes also when there are contents # Removes also when there are contents
file_helper.del_dirs(dir_path) FileHelpers.del_dirs(dir_path)
@tornado.web.authenticated @tornado.web.authenticated
def put(self, page): def put(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
superuser = exec_user['superuser'] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
permissions = { permissions = {
'Commands': Enum_Permissions_Server.Commands, "Commands": EnumPermissionsServer.COMMANDS,
'Terminal': Enum_Permissions_Server.Terminal, "Terminal": EnumPermissionsServer.TERMINAL,
'Logs': Enum_Permissions_Server.Logs, "Logs": EnumPermissionsServer.LOGS,
'Schedule': Enum_Permissions_Server.Schedule, "Schedule": EnumPermissionsServer.SCHEDULE,
'Backup': Enum_Permissions_Server.Backup, "Backup": EnumPermissionsServer.BACKUP,
'Files': Enum_Permissions_Server.Files, "Files": EnumPermissionsServer.FILES,
'Config': Enum_Permissions_Server.Config, "Config": EnumPermissionsServer.CONFIG,
'Players': Enum_Permissions_Server.Players, "Players": EnumPermissionsServer.PLAYERS,
} }
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id) user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
if page == "save_file": if page == "save_file":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
file_contents = self.get_body_argument('file_contents', default=None, strip=True) file_contents = self.get_body_argument(
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True)) "file_contents", default=None, strip=True
)
file_path = Helpers.get_os_understandable_path(
self.get_body_argument("file_path", default=None, strip=True)
)
if not self.check_server_id(server_id, 'save_file'): if not self.check_server_id(server_id, "save_file"):
return return
else: else:
server_id = bleach.clean(server_id) 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)\ if not Helpers.in_path(
or not helper.check_file_exists(os.path.abspath(file_path)): Helpers.get_os_understandable_path(
logger.warning(f"Invalid path in save_file file ajax call ({file_path})") self.controller.servers.get_server_data_by_id(server_id)["path"]
console.warning(f"Invalid path in save_file file ajax call ({file_path})") ),
file_path,
) or not Helpers.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 return
# Open the file in write mode and store the content in file_object # Open the file in write mode and store the content in file_object
with open(file_path, 'w', encoding='utf-8') as file_object: with open(file_path, "w", encoding="utf-8") as file_object:
file_object.write(file_contents) file_object.write(file_contents)
elif page == "rename_file": elif page == "rename_file":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
item_path = helper.get_os_understandable_path(self.get_body_argument('item_path', default=None, strip=True)) item_path = Helpers.get_os_understandable_path(
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True) 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'): if not self.check_server_id(server_id, "rename_file"):
return return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if item_path is None or new_item_name is None: if item_path is None or new_item_name is None:
logger.warning("Invalid path(s) in rename_file file ajax call") logger.warning("Invalid path(s) in rename_file file ajax call")
console.warning("Invalid path(s) in rename_file file ajax call") Console.warning("Invalid path(s) in rename_file file ajax call")
return return
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), item_path) \ if not Helpers.in_path(
or not helper.check_path_exists(os.path.abspath(item_path)): Helpers.get_os_understandable_path(
logger.warning(f"Invalid old name path in rename_file file ajax call ({server_id})") self.controller.servers.get_server_data_by_id(server_id)["path"]
console.warning(f"Invalid old name path in rename_file file ajax call ({server_id})") ),
item_path,
) or not Helpers.check_path_exists(os.path.abspath(item_path)):
logger.warning(
f"Invalid old name path in rename_file file ajax call ({server_id})"
)
Console.warning(
f"Invalid old name path in rename_file file ajax call ({server_id})"
)
return return
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name) new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), if not Helpers.in_path(
new_item_path) \ Helpers.get_os_understandable_path(
or helper.check_path_exists(os.path.abspath(new_item_path)): self.controller.servers.get_server_data_by_id(server_id)["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})") new_item_path,
) or Helpers.check_path_exists(os.path.abspath(new_item_path)):
logger.warning(
f"Invalid new name path in rename_file file ajax call ({server_id})"
)
Console.warning(
f"Invalid new name path in rename_file file ajax call ({server_id})"
)
return return
# RENAME # RENAME
os.rename(item_path, new_item_path) os.rename(item_path, new_item_path)
@tornado.web.authenticated @tornado.web.authenticated
def patch(self, page): def patch(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user
superuser = exec_user['superuser'] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None) server_id = self.get_argument("id", None)
permissions = { permissions = {
'Commands': Enum_Permissions_Server.Commands, "Commands": EnumPermissionsServer.COMMANDS,
'Terminal': Enum_Permissions_Server.Terminal, "Terminal": EnumPermissionsServer.TERMINAL,
'Logs': Enum_Permissions_Server.Logs, "Logs": EnumPermissionsServer.LOGS,
'Schedule': Enum_Permissions_Server.Schedule, "Schedule": EnumPermissionsServer.SCHEDULE,
'Backup': Enum_Permissions_Server.Backup, "Backup": EnumPermissionsServer.BACKUP,
'Files': Enum_Permissions_Server.Files, "Files": EnumPermissionsServer.FILES,
'Config': Enum_Permissions_Server.Config, "Config": EnumPermissionsServer.CONFIG,
'Players': Enum_Permissions_Server.Players, "Players": EnumPermissionsServer.PLAYERS,
} }
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id) user_perms = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
if page == "rename_file": if page == "rename_file":
if not permissions['Files'] in user_perms: if not permissions["Files"] in user_perms:
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files") self.redirect("/panel/error?error=Unauthorized access to Files")
return return
item_path = helper.get_os_understandable_path(self.get_body_argument('item_path', default=None, strip=True)) item_path = Helpers.get_os_understandable_path(
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True) 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'): if not self.check_server_id(server_id, "rename_file"):
return return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
if item_path is None or new_item_name is None: if item_path is None or new_item_name is None:
logger.warning("Invalid path(s) in rename_file file ajax call") logger.warning("Invalid path(s) in rename_file file ajax call")
console.warning("Invalid path(s) in rename_file file ajax call") Console.warning("Invalid path(s) in rename_file file ajax call")
return return
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), item_path) \ if not Helpers.in_path(
or not helper.check_path_exists(os.path.abspath(item_path)): Helpers.get_os_understandable_path(
logger.warning(f"Invalid old name path in rename_file file ajax call ({server_id})") self.controller.servers.get_server_data_by_id(server_id)["path"]
console.warning(f"Invalid old name path in rename_file file ajax call ({server_id})") ),
item_path,
) or not Helpers.check_path_exists(os.path.abspath(item_path)):
logger.warning(
f"Invalid old name path in rename_file file ajax call ({server_id})"
)
Console.warning(
f"Invalid old name path in rename_file file ajax call ({server_id})"
)
return return
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name) new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), if not Helpers.in_path(
new_item_path) \ Helpers.get_os_understandable_path(
or helper.check_path_exists(os.path.abspath(new_item_path)): self.controller.servers.get_server_data_by_id(server_id)["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})") new_item_path,
) or Helpers.check_path_exists(os.path.abspath(new_item_path)):
logger.warning(
f"Invalid new name path in rename_file file ajax call ({server_id})"
)
Console.warning(
f"Invalid new name path in rename_file file ajax call ({server_id})"
)
return return
# RENAME # RENAME
@ -400,15 +505,23 @@ class FileHandler(BaseHandler):
def check_server_id(self, server_id, page_name): def check_server_id(self, server_id, page_name):
if server_id is None: if server_id is None:
logger.warning(f"Server ID not defined in {page_name} file ajax call ({server_id})") logger.warning(
console.warning(f"Server ID not defined in {page_name} file ajax call ({server_id})") 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 return
else: else:
server_id = bleach.clean(server_id) server_id = bleach.clean(server_id)
# does this server id exist? # does this server id exist?
if not self.controller.servers.server_id_exists(server_id): 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})") logger.warning(
console.warning(f"Server ID not found in {page_name} file ajax call ({server_id})") 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
return True return True

View File

@ -1,32 +1,27 @@
import logging import logging
import requests
from app.classes.shared.helpers import helper
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
try:
import requests
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class HTTPHandler(BaseHandler): class HTTPHandler(BaseHandler):
def get(self): def get(self):
url = str(self.request.host) url = str(self.request.host)
port = 443 port = 443
url_list = url.split(":") url_list = url.split(":")
if url_list[0] != "": if url_list[0] != "":
url = 'https://' + url_list[0] url = "https://" + url_list[0]
else: else:
url = 'https://' + url url = "https://" + url
db_port = helper.get_setting('https_port') db_port = self.helper.get_setting("https_port")
try: try:
resp = requests.get(url + ":" + str(port)) resp = requests.get(url + ":" + str(port))
resp.raise_for_status() resp.raise_for_status()
except Exception: except Exception:
port = db_port port = db_port
self.redirect(url+":"+str(port)) self.redirect(url + ":" + str(port))
class HTTPHandlerPage(BaseHandler): class HTTPHandlerPage(BaseHandler):
@ -35,13 +30,13 @@ class HTTPHandlerPage(BaseHandler):
port = 443 port = 443
url_list = url.split(":") url_list = url.split(":")
if url_list[0] != "": if url_list[0] != "":
url = 'https://' + url_list[0] url = "https://" + url_list[0]
else: else:
url = 'https://' + url url = "https://" + url
db_port = helper.get_setting('https_port') db_port = self.helper.get_setting("https_port")
try: try:
resp = requests.get(url + ":" + str(port)) resp = requests.get(url + ":" + str(port))
resp.raise_for_status() resp.raise_for_status()
except Exception: except Exception:
port = db_port port = db_port
self.redirect(url+":"+str(port)) self.redirect(url + ":" + str(port))

View File

@ -1,26 +1,28 @@
import logging import logging
import requests import requests
from app.classes.shared.helpers import helper
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class HTTPHandlerPage(BaseHandler): class HTTPHandlerPage(BaseHandler):
def get(self): def get(self):
url = self.request.full_url url = self.request.full_url
port = 443 port = 443
if url[len(url)-1] == '/': if url[len(url) - 1] == "/":
url = url.strip(url[len(url)-1]) url = url.strip(url[len(url) - 1])
url_list = url.split('/') url_list = url.split("/")
if url_list[0] != "": if url_list[0] != "":
primary_url = url_list[0] + ":"+str(port)+"/" primary_url = url_list[0] + ":" + str(port) + "/"
backup_url = url_list[0] + ":" +str(helper.get_setting("https_port")) +"/" backup_url = (
for i in range(len(url_list)-1): url_list[0] + ":" + str(self.helper.get_setting("https_port")) + "/"
primary_url += url_list[i+1] )
backup_url += url_list[i+1] for i in range(len(url_list) - 1):
primary_url += url_list[i + 1]
backup_url += url_list[i + 1]
else: else:
primary_url = url + str(port) primary_url = url + str(port)
backup_url = url + str(helper.get_setting('https_port')) backup_url = url + str(self.helper.get_setting("https_port"))
try: try:
resp = requests.get(primary_url) resp = requests.get(primary_url)
@ -28,4 +30,4 @@ class HTTPHandlerPage(BaseHandler):
url = primary_url url = primary_url
except Exception: except Exception:
url = backup_url 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

View File

@ -1,44 +1,51 @@
import logging import logging
import bleach
from app.classes.models.users import Users from app.classes.shared.helpers import Helpers
from app.classes.shared.authentication import authentication from app.classes.models.users import HelperUsers
from app.classes.shared.helpers import helper
from app.classes.shared.main_models import fn
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
try:
import bleach
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PublicHandler(BaseHandler):
class PublicHandler(BaseHandler):
def set_current_user(self, user_id: str = None): def set_current_user(self, user_id: str = None):
expire_days = helper.get_setting('cookie_expire') expire_days = self.helper.get_setting("cookie_expire")
# if helper comes back with false # if helper comes back with false
if not expire_days: if not expire_days:
expire_days = "5" expire_days = "5"
if user_id is not None: if user_id is not None:
self.set_cookie("token", authentication.generate(user_id), expires_days=int(expire_days)) self.set_cookie(
"token",
self.controller.authentication.generate(user_id),
expires_days=int(expire_days),
)
else: else:
self.clear_cookie("user") self.clear_cookie("token")
# self.clear_cookie("user")
# self.clear_cookie("user_data")
def get(self, page=None): def get(self, page=None):
error = bleach.clean(self.get_argument('error', "Invalid Login!")) error = bleach.clean(self.get_argument("error", "Invalid Login!"))
error_msg = bleach.clean(self.get_argument('error_msg', '')) error_msg = bleach.clean(self.get_argument("error_msg", ""))
try:
page_data = { page_data = {
'version': helper.get_version_string(), "version": self.helper.get_version_string(),
'error': error, 'lang': helper.get_setting('language'), "error": error,
'lang_page': helper.getLangPage(helper.get_setting('language')) "lang": self.helper.get_setting("language"),
} "lang_page": self.helper.get_lang_page(
self.helper.get_setting("language")
),
"query": "",
}
except:
self.redirect("/public/login.html")
if self.request.query:
page_data["query"] = self.request.query
# sensible defaults # sensible defaults
template = "public/404.html" template = "public/404.html"
@ -53,75 +60,141 @@ class PublicHandler(BaseHandler):
template = "public/error.html" template = "public/error.html"
elif page == "logout": elif page == "logout":
self.clear_cookie("user") self.clear_cookie("token")
self.clear_cookie("user_data") # self.clear_cookie("user")
self.redirect('/public/login') # self.clear_cookie("user_data")
self.redirect("/public/login")
return return
# if we have no page, let's go to login # if we have no page, let's go to login
else: else:
self.redirect('/public/login') if self.request.query:
self.redirect("/public/login?" + self.request.query)
else:
self.redirect("/public/login")
return return
self.render( self.render(
template, template,
data=page_data, data=page_data,
translate=self.translator.translate, translate=self.translator.translate,
error_msg = error_msg error_msg=error_msg,
) )
def post(self, page=None): def post(self, page=None):
if page == 'login': error = bleach.clean(self.get_argument("error", "Invalid Login!"))
next_page = "/public/login" error_msg = bleach.clean(self.get_argument("error_msg", ""))
entered_username = bleach.clean(self.get_argument('username')) page_data = {
entered_password = bleach.clean(self.get_argument('password')) "version": self.helper.get_version_string(),
"error": error,
"lang": self.helper.get_setting("language"),
"lang_page": self.helper.get_lang_page(self.helper.get_setting("language")),
"query": "",
}
if self.request.query:
page_data["query"] = self.request.query
if page == "login":
next_page = "/public/login"
if self.request.query:
next_page = "/public/login?" + self.request.query
entered_username = bleach.clean(self.get_argument("username"))
entered_password = bleach.clean(self.get_argument("password"))
# pylint: disable=no-member # pylint: disable=no-member
user_data = Users.get_or_none(fn.Lower(Users.username) == entered_username.lower()) try:
user_id = HelperUsers.get_user_id_by_name(entered_username.lower())
user_data = HelperUsers.get_user_model(user_id)
except:
error_msg = "Incorrect username or password. Please try again."
# self.clear_cookie("user")
# self.clear_cookie("user_data")
self.clear_cookie("token")
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else:
self.redirect(f"/public/login?error_msg={error_msg}")
return
# if we don't have a user # if we don't have a user
if not user_data: if not user_data:
error_msg = "Incorrect username or password. Please try again." error_msg = "Incorrect username or password. Please try again."
self.clear_cookie("user") # self.clear_cookie("user")
self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.redirect(f'/public/login?error_msg={error_msg}') self.clear_cookie("token")
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else:
self.redirect(f"/public/login?error_msg={error_msg}")
return return
# if they are disabled # if they are disabled
if not user_data.enabled: if not user_data.enabled:
error_msg = "User account disabled. Please contact your system administrator for more info." error_msg = (
self.clear_cookie("user") "User account disabled. Please contact "
self.clear_cookie("user_data") "your system administrator for more info."
self.redirect(f'/public/login?error_msg={error_msg}') )
# self.clear_cookie("user")
# self.clear_cookie("user_data")
self.clear_cookie("token")
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else:
self.redirect(f"/public/login?error_msg={error_msg}")
return return
login_result = helper.verify_pass(entered_password, user_data.password) login_result = self.helper.verify_pass(entered_password, user_data.password)
# Valid Login # Valid Login
if login_result: if login_result:
self.set_current_user(user_data.user_id) self.set_current_user(user_data.user_id)
logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}") logger.info(
f"User: {user_data} Logged in from IP: {self.get_remote_ip()}"
)
# record this login # record this login
q = Users.select().where(Users.username == entered_username.lower()).get() user_data.last_ip = self.get_remote_ip()
q.last_ip = self.get_remote_ip() user_data.last_login = Helpers.get_time_as_string()
q.last_login = helper.get_time_as_string() user_data.save()
q.save()
# log this login # log this login
self.controller.management.add_to_audit_log(user_data.user_id, "Logged in", 0, self.get_remote_ip()) self.controller.management.add_to_audit_log(
user_data.user_id, "Logged in", 0, self.get_remote_ip()
)
if self.request.query_arguments.get("next"):
next_page = self.request.query_arguments.get("next")[0].decode()
else:
next_page = "/panel/dashboard"
next_page = "/panel/dashboard"
self.redirect(next_page) self.redirect(next_page)
else: else:
self.clear_cookie("user") # self.clear_cookie("user")
self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token")
error_msg = "Inncorrect username or password. Please try again." error_msg = "Inncorrect username or password. Please try again."
# log this failed login attempt # 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.controller.management.add_to_audit_log(
self.redirect(f'/public/login?error_msg={error_msg}') user_data.user_id, "Tried to log in", 0, self.get_remote_ip()
)
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
else:
self.redirect(f"/public/login?error_msg={error_msg}")
else: else:
self.redirect("/public/login") if self.request.query:
self.redirect("/public/login?" + self.request.query)
else:
self.redirect("/public/login")

View File

@ -0,0 +1,157 @@
from app.classes.web.routes.api.index_handler import ApiIndexHandler
from app.classes.web.routes.api.jsonschema import (
ApiJsonSchemaHandler,
ApiJsonSchemaListHandler,
)
from app.classes.web.routes.api.not_found import ApiNotFoundHandler
from app.classes.web.routes.api.auth.invalidate_tokens import (
ApiAuthInvalidateTokensHandler,
)
from app.classes.web.routes.api.auth.login import ApiAuthLoginHandler
from app.classes.web.routes.api.roles.index import ApiRolesIndexHandler
from app.classes.web.routes.api.roles.role.index import ApiRolesRoleIndexHandler
from app.classes.web.routes.api.roles.role.servers import ApiRolesRoleServersHandler
from app.classes.web.routes.api.roles.role.users import ApiRolesRoleUsersHandler
from app.classes.web.routes.api.servers.index import ApiServersIndexHandler
from app.classes.web.routes.api.servers.server.action import (
ApiServersServerActionHandler,
)
from app.classes.web.routes.api.servers.server.index import ApiServersServerIndexHandler
from app.classes.web.routes.api.servers.server.logs import ApiServersServerLogsHandler
from app.classes.web.routes.api.servers.server.public import (
ApiServersServerPublicHandler,
)
from app.classes.web.routes.api.servers.server.stats import ApiServersServerStatsHandler
from app.classes.web.routes.api.servers.server.users import ApiServersServerUsersHandler
from app.classes.web.routes.api.users.index import ApiUsersIndexHandler
from app.classes.web.routes.api.users.user.index import ApiUsersUserIndexHandler
from app.classes.web.routes.api.users.user.pfp import ApiUsersUserPfpHandler
from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandler
def api_handlers(handler_args):
return [
# Auth routes
(
r"/api/v2/auth/login/?",
ApiAuthLoginHandler,
handler_args,
),
(
r"/api/v2/auth/invalidate_tokens/?",
ApiAuthInvalidateTokensHandler,
handler_args,
),
# User routes
(
r"/api/v2/users/?",
ApiUsersIndexHandler,
handler_args,
),
(
r"/api/v2/users/([0-9]+)/?",
ApiUsersUserIndexHandler,
handler_args,
),
(
r"/api/v2/users/(@me)/?",
ApiUsersUserIndexHandler,
handler_args,
),
(
r"/api/v2/users/([0-9]+)/pfp/?",
ApiUsersUserPfpHandler,
handler_args,
),
(
r"/api/v2/users/(@me)/pfp/?",
ApiUsersUserPfpHandler,
handler_args,
),
(
r"/api/v2/users/([0-9]+)/public/?",
ApiUsersUserPublicHandler,
handler_args,
),
(
r"/api/v2/users/(@me)/public/?",
ApiUsersUserPublicHandler,
handler_args,
),
# Server routes
(
r"/api/v2/servers/?",
ApiServersIndexHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/?",
ApiServersServerIndexHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/stats/?",
ApiServersServerStatsHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/action/([a-z_]+)/?",
ApiServersServerActionHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/logs/?",
ApiServersServerLogsHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/users/?",
ApiServersServerUsersHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/public/?",
ApiServersServerPublicHandler,
handler_args,
),
(
r"/api/v2/roles/?",
ApiRolesIndexHandler,
handler_args,
),
(
r"/api/v2/roles/([0-9]+)/?",
ApiRolesRoleIndexHandler,
handler_args,
),
(
r"/api/v2/roles/([0-9]+)/servers/?",
ApiRolesRoleServersHandler,
handler_args,
),
(
r"/api/v2/roles/([0-9]+)/users/?",
ApiRolesRoleUsersHandler,
handler_args,
),
(
r"/api/v2/jsonschema/?",
ApiJsonSchemaListHandler,
handler_args,
),
(
r"/api/v2/jsonschema/([a-z0-9_]+)/?",
ApiJsonSchemaHandler,
handler_args,
),
(
r"/api/v2/?",
ApiIndexHandler,
handler_args,
),
(
r"/api/v2/(.*)",
ApiNotFoundHandler,
handler_args,
),
]

View File

@ -0,0 +1,21 @@
import datetime
import logging
from app.classes.shared.console import Console
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiAuthInvalidateTokensHandler(BaseApiHandler):
def post(self):
auth_data = self.authenticate_user()
if not auth_data:
return
# TODO: Invalidate tokens
Console.info("invalidate_tokens")
self.controller.users.raw_update_user(
auth_data[4]["user_id"], {"valid_tokens_from": datetime.datetime.now()}
)
self.finish_json(200, {"status": "ok"})

View File

@ -0,0 +1,104 @@
import logging
import json
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from app.classes.models.users import Users
from app.classes.shared.helpers import Helpers
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
login_schema = {
"type": "object",
"properties": {
"username": {
"type": "string",
"maxLength": 20,
"minLength": 4,
"pattern": "^[a-z0-9_]+$",
},
"password": {"type": "string", "maxLength": 20, "minLength": 4},
},
"required": ["username", "password"],
"additionalProperties": False,
}
class ApiAuthLoginHandler(BaseApiHandler):
def post(self):
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, login_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
username = data["username"]
password = data["password"]
# pylint: disable=no-member
user_data = Users.get_or_none(Users.username == username)
if user_data is None:
return self.finish_json(
401,
{"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None},
)
if not user_data.enabled:
self.finish_json(
403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None}
)
return
login_result = self.helper.verify_pass(password, user_data.password)
# Valid Login
if login_result:
logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}")
# record this login
query = Users.select().where(Users.username == username.lower()).get()
query.last_ip = self.get_remote_ip()
query.last_login = Helpers.get_time_as_string()
query.save()
# log this login
self.controller.management.add_to_audit_log(
user_data.user_id, "logged in via the API", 0, self.get_remote_ip()
)
self.finish_json(
200,
{
"status": "ok",
"data": {
"token": self.controller.authentication.generate(
user_data.user_id
),
"user_id": str(user_data.user_id),
},
},
)
else:
# 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.finish_json(
401,
{"status": "error", "error": "INCORRECT_CREDENTIALS"},
)

View File

@ -0,0 +1,2 @@
# nothing here yet
# sometime implement configurable self service account creation?

View File

@ -0,0 +1,17 @@
from app.classes.web.base_api_handler import BaseApiHandler
WIKI_API_LINK = "https://wiki.craftycontrol.com/en/4/docs/API V2"
class ApiIndexHandler(BaseApiHandler):
def get(self):
self.finish_json(
200,
{
"status": "ok",
"data": {
"version": self.controller.helper.get_version_string(),
"message": f"Please see the API documentation at {WIKI_API_LINK}",
},
},
)

View File

@ -0,0 +1,107 @@
import typing as t
from app.classes.web.base_api_handler import BaseApiHandler
from app.classes.web.routes.api.auth.login import login_schema
from app.classes.web.routes.api.roles.role.index import modify_role_schema
from app.classes.web.routes.api.roles.index import create_role_schema
from app.classes.web.routes.api.servers.server.index import server_patch_schema
from app.classes.web.routes.api.servers.index import new_server_schema
SCHEMA_LIST: t.Final = [
"login",
"modify_role",
"create_role",
"server_patch",
"new_server",
"user_patch",
"new_user",
]
class ApiJsonSchemaListHandler(BaseApiHandler):
def get(self):
self.finish_json(
200,
{"status": "ok", "data": SCHEMA_LIST},
)
class ApiJsonSchemaHandler(BaseApiHandler):
def get(self, schema_name: str):
if schema_name == "login":
self.finish_json(
200,
{"status": "ok", "data": login_schema},
)
elif schema_name == "modify_role":
self.finish_json(
200,
{"status": "ok", "data": modify_role_schema},
)
elif schema_name == "create_role":
self.finish_json(
200,
{"status": "ok", "data": create_role_schema},
)
elif schema_name == "server_patch":
self.finish_json(200, server_patch_schema)
elif schema_name == "new_server":
self.finish_json(
200,
new_server_schema,
)
elif schema_name == "user_patch":
self.finish_json(
200,
{
"status": "ok",
"data": {
"type": "object",
"properties": {
**self.controller.users.user_jsonschema_props,
},
"anyOf": [
# Require at least one property
{"required": [name]}
for name in [
"username",
"password",
"email",
"enabled",
"lang",
"superuser",
"permissions",
"roles",
"hints",
]
],
"additionalProperties": False,
},
},
)
elif schema_name == "new_user":
self.finish_json(
200,
{
"status": "ok",
"data": {
"type": "object",
"properties": {
**self.controller.users.user_jsonschema_props,
},
"required": ["username", "password"],
"additionalProperties": False,
},
},
)
else:
self.finish_json(
404,
{
"status": "error",
"error": "UNKNOWN_JSON_SCHEMA",
"info": (
f"Unknown JSON schema: {schema_name}."
f" Here's a list of all the valid schema names: {SCHEMA_LIST}"
),
},
)

View File

@ -0,0 +1,18 @@
from typing import Awaitable, Callable, Optional
from app.classes.web.base_api_handler import BaseApiHandler
class ApiNotFoundHandler(BaseApiHandler):
def _not_found(self, page: str) -> None:
self.finish_json(
404,
{"status": "error", "error": "API_HANDLER_NOT_FOUND", "page": page},
)
head = _not_found # type: Callable[..., Optional[Awaitable[None]]]
get = _not_found # type: Callable[..., Optional[Awaitable[None]]]
post = _not_found # type: Callable[..., Optional[Awaitable[None]]]
delete = _not_found # type: Callable[..., Optional[Awaitable[None]]]
patch = _not_found # type: Callable[..., Optional[Awaitable[None]]]
put = _not_found # type: Callable[..., Optional[Awaitable[None]]]
options = _not_found # type: Callable[..., Optional[Awaitable[None]]]

View File

@ -0,0 +1,131 @@
import typing as t
from jsonschema import ValidationError, validate
import orjson
from playhouse.shortcuts import model_to_dict
from app.classes.web.base_api_handler import BaseApiHandler
create_role_schema = {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
},
"servers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"server_id": {
"type": "integer",
"minimum": 1,
},
"permissions": {
"type": "string",
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
},
},
"required": ["server_id", "permissions"],
},
},
},
"required": ["name"],
"additionalProperties": False,
}
class ApiRolesIndexHandler(BaseApiHandler):
def get(self):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
# GET /api/v2/roles?ids=true
get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(
200,
{
"status": "ok",
"data": self.controller.roles.get_all_role_ids()
if get_only_ids
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()],
},
)
def post(self):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
user,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = orjson.loads(self.request.body) # pylint: disable=no-member
except orjson.decoder.JSONDecodeError as e: # pylint: disable=no-member
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, create_role_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
role_name = data["name"]
# Get the servers
servers_dict = {server["server_id"]: server for server in data["servers"]}
server_ids = (
(
{server["server_id"] for server in data["servers"]}
& set(self.controller.get_all_server_ids())
) # Only allow existing servers
if "servers" in data
else set()
)
servers: t.List[dict] = [servers_dict[server_id] for server_id in server_ids]
if self.controller.roles.get_roleid_by_name(role_name) is not None:
return self.finish_json(
400, {"status": "error", "error": "ROLE_NAME_ALREADY_EXISTS"}
)
role_id = self.controller.roles.add_role_advanced(role_name, servers)
self.controller.management.add_to_audit_log(
user["user_id"],
f"created role {role_name} (RID:{role_id})",
server_id=0,
source_ip=self.get_remote_ip(),
)
self.finish_json(
200,
{"status": "ok", "data": {"role_id": role_id}},
)

View File

@ -0,0 +1,143 @@
from jsonschema import ValidationError, validate
import orjson
from peewee import DoesNotExist
from app.classes.web.base_api_handler import BaseApiHandler
modify_role_schema = {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
},
"servers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"server_id": {
"type": "integer",
"minimum": 1,
},
"permissions": {
"type": "string",
"pattern": "^[01]{8}$", # 8 bits, see EnumPermissionsServer
},
},
"required": ["server_id", "permissions"],
},
},
},
"anyOf": [
{"required": ["name"]},
{"required": ["servers"]},
],
"additionalProperties": False,
}
class ApiRolesRoleIndexHandler(BaseApiHandler):
def get(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
self.finish_json(
200,
{"status": "ok", "data": self.controller.roles.get_role(role_id)},
)
except DoesNotExist:
self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
def delete(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
user,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.controller.roles.remove_role(role_id)
self.finish_json(
200,
{"status": "ok", "data": role_id},
)
self.controller.management.add_to_audit_log(
user["user_id"],
f"deleted role with ID {role_id}",
server_id=0,
source_ip=self.get_remote_ip(),
)
def patch(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
user,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = orjson.loads(self.request.body) # pylint: disable=no-member
except orjson.decoder.JSONDecodeError as e: # pylint: disable=no-member
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, modify_role_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
try:
self.controller.roles.update_role_advanced(
role_id, data.get("role_name", None), data.get("servers", None)
)
except DoesNotExist:
return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
self.controller.management.add_to_audit_log(
user["user_id"],
f"modified role with ID {role_id}",
server_id=0,
source_ip=self.get_remote_ip(),
)
self.finish_json(
200,
{"status": "ok"},
)

View File

@ -0,0 +1,32 @@
from app.classes.models.server_permissions import PermissionsServers
from app.classes.web.base_api_handler import BaseApiHandler
class ApiRolesRoleServersHandler(BaseApiHandler):
def get(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
# GET /api/v2/roles/role/servers?ids=true
get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(
200,
{
"status": "ok",
"data": PermissionsServers.get_server_ids_from_role(role_id)
if get_only_ids
else self.controller.roles.get_server_ids_and_perms_from_role(role_id),
},
)

View File

@ -0,0 +1,36 @@
from app.classes.web.base_api_handler import BaseApiHandler
class ApiRolesRoleUsersHandler(BaseApiHandler):
def get(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
all_user_ids = self.controller.users.get_all_user_ids()
user_roles = {}
for user_id in all_user_ids:
user_roles_list = self.controller.users.get_user_roles_names(user_id)
user_roles[user_id] = user_roles_list
role = self.controller.roles.get_role(role_id)
user_ids = []
for user_id in all_user_ids:
for role_user in user_roles[user_id]:
if role_user == role["role_name"]:
user_ids.append(user_id)
self.finish_json(200, {"status": "ok", "data": user_ids})

View File

@ -0,0 +1,713 @@
import logging
from jsonschema import ValidationError, validate
import orjson
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
new_server_schema = {
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Root",
"type": "object",
"required": [
"name",
"monitoring_type",
"create_type",
],
"examples": [
{
"name": "My Server",
"monitoring_type": "minecraft_java",
"minecraft_java_monitoring_data": {"host": "127.0.0.1", "port": 25565},
"create_type": "minecraft_java",
"minecraft_java_create_data": {
"create_type": "download_jar",
"download_jar_create_data": {
"type": "Paper",
"version": "1.18.2",
"mem_min": 1,
"mem_max": 2,
"server_properties_port": 25565,
},
},
}
],
"properties": {
"name": {
"title": "Name",
"type": "string",
"examples": ["My Server"],
"minLength": 2,
},
"stop_command": {
"title": "Stop command",
"description": '"" means the default for the server creation type.',
"type": "string",
"default": "",
"examples": ["stop", "end"],
},
"log_location": {
"title": "Log file",
"description": '"" means the default for the server creation type.',
"type": "string",
"default": "",
"examples": ["./logs/latest.log", "./proxy.log.0"],
},
"crashdetection": {
"title": "Crash detection",
"type": "boolean",
"default": False,
},
"autostart": {
"title": "Autostart",
"description": "If true, the server will be started"
+ " automatically when Crafty is launched.",
"type": "boolean",
"default": False,
},
"autostart_delay": {
"title": "Autostart delay",
"description": "Delay in seconds before autostarting. (If enabled)",
"type": "number",
"default": 10,
"minimum": 0,
},
"monitoring_type": {
"title": "Server monitoring type",
"type": "string",
"default": "minecraft_java",
"enum": ["minecraft_java", "minecraft_bedrock", "none"],
# TODO: SteamCMD, RakNet, etc.
},
"minecraft_java_monitoring_data": {
"title": "Minecraft Java monitoring data",
"type": "object",
"required": ["host", "port"],
"properties": {
"host": {
"title": "Host",
"type": "string",
"default": "127.0.0.1",
"examples": ["127.0.0.1"],
"minLength": 1,
},
"port": {
"title": "Port",
"type": "integer",
"examples": [25565],
"default": 25565,
"minimum": 0,
},
},
},
"minecraft_bedrock_monitoring_data": {
"title": "Minecraft Bedrock monitoring data",
"type": "object",
"required": ["host", "port"],
"properties": {
"host": {
"title": "Host",
"type": "string",
"default": "127.0.0.1",
"examples": ["127.0.0.1"],
"minLength": 1,
},
"port": {
"title": "Port",
"type": "integer",
"examples": [19132],
"default": 19132,
"minimum": 0,
},
},
},
"create_type": {
# This is only used for creation, this is not saved in the db
"title": "Server creation type",
"type": "string",
"default": "minecraft_java",
"enum": ["minecraft_java", "minecraft_bedrock", "custom"],
},
"minecraft_java_create_data": {
"title": "Java creation data",
"type": "object",
"required": ["create_type"],
"properties": {
"create_type": {
"title": "Creation type",
"type": "string",
"default": "download_jar",
"enum": ["download_jar", "import_server", "import_zip"],
},
"download_jar_create_data": {
"title": "JAR download data",
"type": "object",
"required": [
"type",
"version",
"mem_min",
"mem_max",
"server_properties_port",
"agree_to_eula",
],
"properties": {
"type": {
"title": "Server JAR Type",
"type": "string",
"examples": ["Paper"],
"minLength": 1,
},
"version": {
"title": "Server JAR Version",
"type": "string",
"examples": ["1.18.2"],
"minLength": 1,
},
"mem_min": {
"title": "Minimum JVM memory (in GiBs)",
"type": "number",
"examples": [1],
"default": 1,
"exclusiveMinimum": 0,
},
"mem_max": {
"title": "Maximum JVM memory (in GiBs)",
"type": "number",
"examples": [2],
"default": 2,
"exclusiveMinimum": 0,
},
"server_properties_port": {
"title": "Port",
"type": "integer",
"examples": [25565],
"default": 25565,
"minimum": 0,
},
"agree_to_eula": {
"title": "Agree to the EULA",
"type": "boolean",
"default": False,
},
},
},
"import_server_create_data": {
"title": "Import server data",
"type": "object",
"required": [
"existing_server_path",
"jarfile",
"mem_min",
"mem_max",
"server_properties_port",
"agree_to_eula",
],
"properties": {
"existing_server_path": {
"title": "Server path",
"description": "Absolute path to the old server",
"type": "string",
"examples": ["/var/opt/server"],
"minLength": 1,
},
"jarfile": {
"title": "JAR file",
"description": "The JAR file relative to the previous path",
"type": "string",
"examples": ["paper.jar", "jars/vanilla-1.12.jar"],
"minLength": 1,
},
"mem_min": {
"title": "Minimum JVM memory (in GiBs)",
"type": "number",
"examples": [1],
"default": 1,
"exclusiveMinimum": 0,
},
"mem_max": {
"title": "Maximum JVM memory (in GiBs)",
"type": "number",
"examples": [2],
"default": 2,
"exclusiveMinimum": 0,
},
"server_properties_port": {
"title": "Port",
"type": "integer",
"examples": [25565],
"default": 25565,
"minimum": 0,
},
"agree_to_eula": {
"title": "Agree to the EULA",
"type": "boolean",
"default": False,
},
},
},
"import_zip_create_data": {
"title": "Import ZIP server data",
"type": "object",
"required": [
"zip_path",
"zip_root",
"jarfile",
"mem_min",
"mem_max",
"server_properties_port",
"agree_to_eula",
],
"properties": {
"zip_path": {
"title": "ZIP path",
"description": "Absolute path to the ZIP archive",
"type": "string",
"examples": ["/var/opt/server.zip"],
"minLength": 1,
},
"zip_root": {
"title": "Server root directory",
"description": "The server root in the ZIP archive",
"type": "string",
"examples": ["/", "/paper-server/", "server-1"],
"minLength": 1,
},
"jarfile": {
"title": "JAR file",
"description": "The JAR relative to the configured root",
"type": "string",
"examples": ["paper.jar", "jars/vanilla-1.12.jar"],
"minLength": 1,
},
"mem_min": {
"title": "Minimum JVM memory (in GiBs)",
"type": "number",
"examples": [1],
"default": 1,
"exclusiveMinimum": 0,
},
"mem_max": {
"title": "Maximum JVM memory (in GiBs)",
"type": "number",
"examples": [2],
"default": 2,
"exclusiveMinimum": 0,
},
"server_properties_port": {
"title": "Port",
"type": "integer",
"examples": [25565],
"default": 25565,
"minimum": 0,
},
"agree_to_eula": {
"title": "Agree to the EULA",
"type": "boolean",
"default": False,
},
},
},
},
"allOf": [
{
"$comment": "If..then section",
"allOf": [
{
"if": {
"properties": {"create_type": {"const": "download_jar"}}
},
"then": {"required": ["download_jar_create_data"]},
},
{
"if": {
"properties": {"create_type": {"const": "import_exec"}}
},
"then": {"required": ["import_server_create_data"]},
},
{
"if": {
"properties": {"create_type": {"const": "import_zip"}}
},
"then": {"required": ["import_zip_create_data"]},
},
],
},
{
"title": "Only one creation data",
"oneOf": [
{"required": ["download_jar_create_data"]},
{"required": ["import_server_create_data"]},
{"required": ["import_zip_create_data"]},
],
},
],
},
"minecraft_bedrock_create_data": {
"title": "Minecraft Bedrock creation data",
"type": "object",
"required": ["create_type"],
"properties": {
"create_type": {
"title": "Creation type",
"type": "string",
"default": "import_server",
"enum": ["import_server", "import_zip"],
},
"import_server_create_data": {
"title": "Import server data",
"type": "object",
"required": ["existing_server_path", "command"],
"properties": {
"existing_server_path": {
"title": "Server path",
"description": "Absolute path to the old server",
"type": "string",
"examples": ["/var/opt/server"],
"minLength": 1,
},
"command": {
"title": "Command",
"type": "string",
"default": "echo foo bar baz",
"examples": ["LD_LIBRARY_PATH=. ./bedrock_server"],
"minLength": 1,
},
},
},
"import_zip_create_data": {
"title": "Import ZIP server data",
"type": "object",
"required": ["zip_path", "zip_root", "command"],
"properties": {
"zip_path": {
"title": "ZIP path",
"description": "Absolute path to the ZIP archive",
"type": "string",
"examples": ["/var/opt/server.zip"],
"minLength": 1,
},
"zip_root": {
"title": "Server root directory",
"description": "The server root in the ZIP archive",
"type": "string",
"examples": ["/", "/paper-server/", "server-1"],
"minLength": 1,
},
"command": {
"title": "Command",
"type": "string",
"default": "echo foo bar baz",
"examples": ["LD_LIBRARY_PATH=. ./bedrock_server"],
"minLength": 1,
},
},
},
},
"allOf": [
{
"$comment": "If..then section",
"allOf": [
{
"if": {
"properties": {"create_type": {"const": "import_exec"}}
},
"then": {"required": ["import_server_create_data"]},
},
{
"if": {
"properties": {"create_type": {"const": "import_zip"}}
},
"then": {"required": ["import_zip_create_data"]},
},
],
},
{
"title": "Only one creation data",
"oneOf": [
{"required": ["import_server_create_data"]},
{"required": ["import_zip_create_data"]},
],
},
],
},
"custom_create_data": {
"title": "Custom creation data",
"type": "object",
"required": [
"working_directory",
"executable_update",
"create_type",
],
"properties": {
"working_directory": {
"title": "Working directory",
"description": '"" means the default',
"type": "string",
"default": "",
"examples": ["/mnt/mydrive/server-configs/", "./subdirectory", ""],
},
"executable_update": {
"title": "Executable Updation",
"description": "Also configurable later on and for other servers",
"type": "object",
"required": ["enabled", "file", "url"],
"properties": {
"enabled": {
"title": "Enabled",
"type": "boolean",
"default": False,
},
"file": {
"title": "Executable to update",
"type": "string",
"default": "",
"examples": ["./paper.jar"],
},
"url": {
"title": "URL to download the executable from",
"type": "string",
"default": "",
},
},
},
"create_type": {
"title": "Creation type",
"type": "string",
"default": "raw_exec",
"enum": ["raw_exec", "import_server", "import_zip"],
},
"raw_exec_create_data": {
"title": "Raw execution command create data",
"type": "object",
"required": ["command"],
"properties": {
"command": {
"title": "Command",
"type": "string",
"default": "echo foo bar baz",
"examples": ["caddy start"],
"minLength": 1,
}
},
},
"import_server_create_data": {
"title": "Import server data",
"type": "object",
"required": ["existing_server_path", "command"],
"properties": {
"existing_server_path": {
"title": "Server path",
"description": "Absolute path to the old server",
"type": "string",
"examples": ["/var/opt/server"],
"minLength": 1,
},
"command": {
"title": "Command",
"type": "string",
"default": "echo foo bar baz",
"examples": ["caddy start"],
"minLength": 1,
},
},
},
"import_zip_create_data": {
"title": "Import ZIP server data",
"type": "object",
"required": ["zip_path", "zip_root", "command"],
"properties": {
"zip_path": {
"title": "ZIP path",
"description": "Absolute path to the ZIP archive",
"type": "string",
"examples": ["/var/opt/server.zip"],
"minLength": 1,
},
"zip_root": {
"title": "Server root directory",
"description": "The server root in the ZIP archive",
"type": "string",
"examples": ["/", "/paper-server/", "server-1"],
"minLength": 1,
},
"command": {
"title": "Command",
"type": "string",
"default": "echo foo bar baz",
"examples": ["caddy start"],
"minLength": 1,
},
},
},
},
"allOf": [
{
"$comment": "If..then section",
"allOf": [
{
"if": {
"properties": {"create_type": {"const": "raw_exec"}}
},
"then": {"required": ["raw_exec_create_data"]},
},
{
"if": {
"properties": {
"create_type": {"const": "import_server"}
}
},
"then": {"required": ["import_server_create_data"]},
},
{
"if": {
"properties": {"create_type": {"const": "import_zip"}}
},
"then": {"required": ["import_zip_create_data"]},
},
],
},
{
"title": "Only one creation data",
"oneOf": [
{"required": ["raw_exec_create_data"]},
{"required": ["import_server_create_data"]},
{"required": ["import_zip_create_data"]},
],
},
],
},
},
"allOf": [
{
"$comment": "If..then section",
"allOf": [
# start require creation data
{
"if": {"properties": {"create_type": {"const": "minecraft_java"}}},
"then": {"required": ["minecraft_java_create_data"]},
},
{
"if": {
"properties": {"create_type": {"const": "minecraft_bedrock"}}
},
"then": {"required": ["minecraft_bedrock_create_data"]},
},
{
"if": {"properties": {"create_type": {"const": "custom"}}},
"then": {"required": ["custom_create_data"]},
},
# end require creation data
# start require monitoring data
{
"if": {
"properties": {"monitoring_type": {"const": "minecraft_java"}}
},
"then": {"required": ["minecraft_java_monitoring_data"]},
},
{
"if": {
"properties": {
"monitoring_type": {"const": "minecraft_bedrock"}
}
},
"then": {"required": ["minecraft_bedrock_monitoring_data"]},
},
# end require monitoring data
],
},
{
"title": "Only one creation data",
"oneOf": [
{"required": ["minecraft_java_create_data"]},
{"required": ["minecraft_bedrock_create_data"]},
{"required": ["custom_create_data"]},
],
},
{
"title": "Only one monitoring data",
"oneOf": [
{"required": ["minecraft_java_monitoring_data"]},
{"required": ["minecraft_bedrock_monitoring_data"]},
{"properties": {"monitoring_type": {"const": "none"}}},
],
},
],
}
class ApiServersIndexHandler(BaseApiHandler):
def get(self):
auth_data = self.authenticate_user()
if not auth_data:
return
# TODO: limit some columns for specific permissions
self.finish_json(200, {"status": "ok", "data": auth_data[0]})
def post(self):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
exec_user_crafty_permissions,
_,
_superuser,
user,
) = auth_data
if EnumPermissionsCrafty.SERVER_CREATION not in exec_user_crafty_permissions:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = orjson.loads(self.request.body) # pylint: disable=no-member
except orjson.decoder.JSONDecodeError as e: # pylint: disable=no-member
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, new_server_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
new_server_id, new_server_uuid = self.controller.create_api_server(data)
# Increase the server creation counter
self.controller.crafty_perms.add_server_creation(user["user_id"])
self.controller.stats.record_stats()
self.controller.management.add_to_audit_log(
user["user_id"],
(
f"created server {data['name']}"
f" (ID: {new_server_id})"
f" (UUID: {new_server_uuid})"
),
server_id=new_server_id,
source_ip=self.get_remote_ip(),
)
self.finish_json(
201,
{
"status": "ok",
"data": {
"new_server_id": str(new_server_id),
"new_server_uuid": new_server_uuid,
},
},
)

View File

@ -0,0 +1,98 @@
import logging
import os
from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.models.servers import Servers
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.helpers import Helpers
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiServersServerActionHandler(BaseApiHandler):
def post(self, server_id: str, action: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.COMMANDS
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Commands permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if action == "clone_server":
return self._clone_server(server_id, auth_data[4]["user_id"])
self.controller.management.send_command(
auth_data[4]["user_id"], server_id, self.get_remote_ip(), action
)
self.finish_json(
200,
{"status": "ok"},
)
def _clone_server(self, server_id, user_id):
def is_name_used(name):
return Servers.select().where(Servers.server_name == name).count() != 0
server_data = self.controller.servers.get_server_data_by_id(server_id)
server_uuid = server_data.get("server_uuid")
new_server_name = server_data.get("server_name") + " (Copy)"
name_counter = 1
while is_name_used(new_server_name):
name_counter += 1
new_server_name = server_data.get("server_name") + f" (Copy {name_counter})"
new_server_uuid = Helpers.create_uuid()
while os.path.exists(os.path.join(self.helper.servers_dir, new_server_uuid)):
new_server_uuid = Helpers.create_uuid()
new_server_path = os.path.join(self.helper.servers_dir, new_server_uuid)
self.controller.management.add_to_audit_log(
user_id,
f"is cloning server {server_id} named {server_data.get('server_name')}",
server_id,
self.get_remote_ip(),
)
# copy the old server
FileHelpers.copy_dir(server_data.get("path"), new_server_path)
# TODO get old server DB data to individual variables
new_server_command = str(server_data.get("execution_command")).replace(
server_uuid, new_server_uuid
)
new_server_log_file = str(
self.helper.get_os_understandable_path(server_data.get("log_path"))
).replace(server_uuid, new_server_uuid)
new_server_id = self.controller.servers.create_server(
new_server_name,
new_server_uuid,
new_server_path,
"",
new_server_command,
server_data.get("executable"),
new_server_log_file,
server_data.get("stop_command"),
server_data.get("type"),
server_data.get("server_port"),
)
self.controller.init_all_servers()
self.finish_json(
200,
{"status": "ok", "data": {"new_server_id": str(new_server_id)}},
)

View File

@ -0,0 +1,168 @@
import logging
import json
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from playhouse.shortcuts import model_to_dict
from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
# TODO: modify monitoring
server_patch_schema = {
"type": "object",
"properties": {
"server_name": {"type": "string", "minLength": 1},
"path": {"type": "string", "minLength": 1},
"backup_path": {"type": "string"},
"executable": {"type": "string"},
"log_path": {"type": "string", "minLength": 1},
"execution_command": {"type": "string", "minLength": 1},
"auto_start": {"type": "boolean"},
"auto_start_delay": {"type": "integer"},
"crash_detection": {"type": "boolean"},
"stop_command": {"type": "string"},
"executable_update_url": {"type": "string", "minLength": 1},
"server_ip": {"type": "string", "minLength": 1},
"server_port": {"type": "integer"},
"logs_delete_after": {"type": "integer"},
"type": {"type": "string", "minLength": 1},
},
"anyOf": [
# Require at least one property
{"required": [name]}
for name in [
"server_name",
"path",
"backup_path",
"executable",
"log_path",
"execution_command",
"auto_start",
"auto_start_delay",
"crash_detection",
"stop_command",
"executable_update_url",
"server_ip",
"server_port",
"logs_delete_after",
"type",
]
],
"additionalProperties": False,
}
class ApiServersServerIndexHandler(BaseApiHandler):
def get(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
server_obj = self.controller.servers.get_server_obj(server_id)
server = model_to_dict(server_obj)
# TODO: limit some columns for specific permissions?
self.finish_json(200, {"status": "ok", "data": server})
def patch(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, server_patch_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.CONFIG
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
server_obj = self.controller.servers.get_server_obj(server_id)
for key in data:
# If we don't validate the input there could be security issues
setattr(self, key, data[key])
self.controller.servers.update_server(server_obj)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"modified the server with ID {server_id}",
server_id,
self.get_remote_ip(),
)
return self.finish_json(200, {"status": "ok"})
def delete(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
# DELETE /api/v2/servers/server?files=true
remove_files = self.get_query_argument("files", None) == "true"
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.CONFIG
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
logger.info(
(
"Removing server and all associated files for server: "
if remove_files
else "Removing server from panel for server: "
)
+ self.controller.servers.get_server_friendly_name(server_id)
)
self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, remove_files)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"deleted the server {server_id}",
server_id,
self.get_remote_ip(),
)
self.finish_json(
200,
{"status": "ok"},
)

View File

@ -0,0 +1,73 @@
import html
import logging
import re
from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.shared.server import ServerOutBuf
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiServersServerLogsHandler(BaseApiHandler):
def get(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
# GET /api/v2/servers/server/logs?file=true
read_log_file = self.get_query_argument("file", None) == "true"
# GET /api/v2/servers/server/logs?colors=true
colored_output = self.get_query_argument("colors", None) == "true"
# GET /api/v2/servers/server/logs?raw=true
disable_ansi_strip = self.get_query_argument("raw", None) == "true"
# GET /api/v2/servers/server/logs?html=true
use_html = self.get_query_argument("html", None) == "true"
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.LOGS
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Logs permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
server_data = self.controller.servers.get_server_data_by_id(server_id)
if read_log_file:
log_lines = self.helper.get_setting("max_log_lines")
raw_lines = self.helper.tail_file(
self.helper.get_os_understandable_path(server_data["log_path"]),
log_lines,
)
else:
raw_lines = ServerOutBuf.lines.get(server_id, [])
lines = []
for line in raw_lines:
try:
if not disable_ansi_strip:
line = re.sub(
"(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )", "", line
)
line = re.sub("[A-z]{2}\b\b", "", line)
line = html.escape(line)
if colored_output:
line = self.helper.log_colors(line)
lines.append(line)
except Exception as e:
logger.warning(f"Skipping Log Line due to error: {e}")
if use_html:
for line in lines:
self.write(f"{line}<br />")
else:
self.finish_json(200, {"status": "ok", "data": lines})

View File

@ -0,0 +1,23 @@
import logging
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiServersServerPublicHandler(BaseApiHandler):
def get(self, server_id):
auth_data = self.authenticate_user()
if not auth_data:
return
server_obj = self.controller.servers.get_server_obj(server_id)
self.finish_json(
200,
{
"status": "ok",
"data": {
key: getattr(server_obj, key)
for key in ["server_id", "created", "server_name", "type"]
},
},
)

View File

@ -0,0 +1,28 @@
import logging
from playhouse.shortcuts import model_to_dict
from app.classes.models.server_stats import HelperServerStats
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiServersServerStatsHandler(BaseApiHandler):
def get(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(
200,
{
"status": "ok",
"data": model_to_dict(
HelperServerStats.get_latest_server_stats(server_id)[0]
),
},
)

View File

@ -0,0 +1,31 @@
import logging
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiServersServerUsersHandler(BaseApiHandler):
def get(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if EnumPermissionsCrafty.USER_CONFIG not in auth_data[1]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if EnumPermissionsCrafty.ROLES_CONFIG not in auth_data[1]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(
200,
{
"status": "ok",
"data": list(self.controller.servers.get_authorized_users(server_id)),
},
)

View File

@ -0,0 +1,164 @@
import logging
import json
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.roles import Roles, HelperRoles
from app.classes.models.users import PUBLIC_USER_ATTRS
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiUsersIndexHandler(BaseApiHandler):
def get(self):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
exec_user_crafty_permissions,
_,
_,
user,
) = auth_data
# GET /api/v2/users?ids=true
get_only_ids = self.get_query_argument("ids", None) == "true"
if EnumPermissionsCrafty.USER_CONFIG in exec_user_crafty_permissions:
if get_only_ids:
data = self.controller.users.get_all_user_ids()
else:
data = [
{key: getattr(user_res, key) for key in PUBLIC_USER_ATTRS}
for user_res in self.controller.users.get_all_users().execute()
]
else:
if get_only_ids:
data = [user["user_id"]]
else:
user_res = self.controller.users.get_user_by_id(user["user_id"])
user_res["roles"] = list(
map(HelperRoles.get_role, user_res.get("roles", set()))
)
data = [{key: user_res[key] for key in PUBLIC_USER_ATTRS}]
self.finish_json(
200,
{
"status": "ok",
"data": data,
},
)
def post(self):
new_user_schema = {
"type": "object",
"properties": {
**self.controller.users.user_jsonschema_props,
},
"required": ["username", "password"],
"additionalProperties": False,
}
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
exec_user_crafty_permissions,
_,
superuser,
user,
) = auth_data
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, new_user_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
username = data["username"]
password = data["password"]
email = data.get("email", "default@example.com")
enabled = data.get("enabled", True)
lang = data.get("lang", self.helper.get_setting("language"))
superuser = data.get("superuser", False)
permissions = data.get("permissions", None)
roles = data.get("roles", None)
hints = data.get("hints", True)
if username.lower() in ["system", ""]:
return self.finish_json(
400, {"status": "error", "error": "INVALID_USERNAME"}
)
if self.controller.users.get_id_by_name(username) is not None:
return self.finish_json(400, {"status": "error", "error": "USER_EXISTS"})
if roles is None:
roles = set()
else:
role_ids = [str(role_id) for role_id in Roles.select(Roles.role_id)]
roles = {role for role in roles if str(role) in role_ids}
permissions_mask = "0" * len(EnumPermissionsCrafty.__members__.items())
server_quantity = {
perm.name: 0
for perm in self.controller.crafty_perms.list_defined_crafty_permissions()
}
if permissions is not None:
server_quantity = {}
permissions_mask = list(permissions_mask)
for permission in permissions:
server_quantity[permission["name"]] = permission["quantity"]
permissions_mask[EnumPermissionsCrafty[permission["name"]].value] = (
"1" if permission["enabled"] else "0"
)
permissions_mask = "".join(permissions_mask)
# TODO: do this in the most efficient way
user_id = self.controller.users.add_user(
username,
password,
email,
enabled,
superuser,
)
self.controller.users.update_user(
user_id,
{"roles": roles, "lang": lang, "hints": hints},
{
"permissions_mask": permissions_mask,
"server_quantity": server_quantity,
},
)
self.controller.management.add_to_audit_log(
user["user_id"],
f"added user {username} (UID:{user_id}) with roles {roles}",
server_id=0,
source_ip=self.get_remote_ip(),
)
self.finish_json(
201,
{"status": "ok", "data": {"user_id": str(user_id)}},
)

View File

@ -0,0 +1,241 @@
import json
import logging
from jsonschema import ValidationError, validate
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.roles import HelperRoles
from app.classes.models.users import HelperUsers
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiUsersUserIndexHandler(BaseApiHandler):
def get(self, user_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
exec_user_crafty_permissions,
_,
_,
user,
) = auth_data
if user_id in ["@me", user["user_id"]]:
user_id = user["user_id"]
res_user = user
elif EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
},
)
else:
# has User_Config permission and isn't viewing self
res_user = self.controller.users.get_user_by_id(user_id)
if not res_user:
return self.finish_json(
404,
{
"status": "error",
"error": "USER_NOT_FOUND",
},
)
# Remove password and valid_tokens_from from the response
# as those should never be sent out to the client.
res_user.pop("password", None)
res_user.pop("valid_tokens_from", None)
res_user["roles"] = list(
map(HelperRoles.get_role, res_user.get("roles", set()))
)
self.finish_json(
200,
{"status": "ok", "data": res_user},
)
def delete(self, user_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
exec_user_crafty_permissions,
_,
_,
user,
) = auth_data
if (user_id in ["@me", user["user_id"]]) and self.helper.get_setting(
"allow_self_delete", False
):
user_id = user["user_id"]
self.controller.users.remove_user(user_id)
elif EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
},
)
else:
# has User_Config permission
self.controller.users.remove_user(user_id)
self.controller.management.add_to_audit_log(
user["user_id"],
f"deleted the user {user_id}",
server_id=0,
source_ip=self.get_remote_ip(),
)
self.finish_json(
200,
{"status": "ok"},
)
def patch(self, user_id: str):
user_patch_schema = {
"type": "object",
"properties": {
**self.controller.users.user_jsonschema_props,
},
"anyOf": [
# Require at least one property
{"required": [name]}
for name in [
"username",
"password",
"email",
"enabled",
"lang",
"superuser",
"permissions",
"roles",
"hints",
]
],
"additionalProperties": False,
}
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
exec_user_crafty_permissions,
_,
superuser,
user,
) = auth_data
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, user_patch_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
if user_id == "@me":
user_id = user["user_id"]
if (
EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions
and str(user["user_id"]) != str(user_id)
):
# If doesn't have perm can't edit other users
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
},
)
if data.get("username", None) is not None:
if data["username"].lower() in ["system", ""]:
return self.finish_json(
400, {"status": "error", "error": "INVALID_USERNAME"}
)
if self.controller.users.get_id_by_name(data["username"]) is not None:
return self.finish_json(
400, {"status": "error", "error": "USER_EXISTS"}
)
if data.get("superuser", None) is not None:
if str(user["user_id"]) == str(user_id):
# Checks if user is trying to change super user status of self.
# We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_SUPERUSER_MODIFY"}
)
if not superuser:
# The user is not superuser so they can't change the superuser status
data.pop("superuser")
if data.get("permissions", None) is not None:
if str(user["user_id"]) == str(user_id):
# Checks if user is trying to change permissions of self.
# We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_PERMISSIONS_MODIFY"}
)
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
# Checks if user is trying to change permissions of someone
# else without User Config permission. We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_PERMISSIONS_MODIFY"}
)
if data.get("roles", None) is not None:
if str(user["user_id"]) == str(user_id):
# Checks if user is trying to change roles of self.
# We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
)
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
# Checks if user is trying to change roles of someone
# else without User Config permission. We don't want that.
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
)
# TODO: make this more efficient
# TODO: add permissions and roles because I forgot
user_obj = HelperUsers.get_user_model(user_id)
self.controller.management.add_to_audit_log(
user["user_id"],
(
f"edited user {user_obj.username} (UID: {user_id})"
f"with roles {user_obj.roles}"
),
server_id=0,
source_ip=self.get_remote_ip(),
)
for key in data:
# If we don't validate the input there could be security issues
setattr(user_obj, key, data[key])
user_obj.save()
return self.finish_json(200, {"status": "ok"})

View File

@ -0,0 +1,49 @@
import logging
import libgravatar
import requests
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiUsersUserPfpHandler(BaseApiHandler):
def get(self, user_id):
auth_data = self.authenticate_user()
if not auth_data:
return
if user_id == "@me":
user = auth_data[4]
else:
user = self.controller.users.get_user_by_id(user_id)
logger.debug(
f'User {auth_data[4]["user_id"]} is fetching the pfp for user {user_id}'
)
# http://en.gravatar.com/site/implement/images/#rating
if self.helper.get_setting("allow_nsfw_profile_pictures"):
rating = "x"
else:
rating = "g"
# Get grvatar hash for profile pictures
if user["email"] != "default@example.com" or "":
gravatar = libgravatar.Gravatar(libgravatar.sanitize_email(user["email"]))
url = gravatar.get_image(
size=80,
default="404",
force_default=False,
rating=rating,
filetype_extension=False,
use_ssl=True,
)
try:
requests.head(url).raise_for_status()
except requests.HTTPError as e:
logger.debug("Gravatar profile picture not found", exc_info=e)
else:
self.finish_json(200, {"status": "ok", "data": url})
return
self.finish_json(200, {"status": "ok", "data": None})

View File

@ -0,0 +1,37 @@
import logging
from app.classes.models.roles import HelperRoles
from app.classes.models.users import PUBLIC_USER_ATTRS
from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__)
class ApiUsersUserPublicHandler(BaseApiHandler):
def get(self, user_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
_,
user,
) = auth_data
if user_id == "@me":
user_id = user["user_id"]
public_user = user
else:
public_user = self.controller.users.get_user_by_id(user_id)
public_user = {key: public_user.get(key) for key in PUBLIC_USER_ATTRS}
public_user["roles"] = list(
map(HelperRoles.get_role, public_user.get("roles", set()))
)
self.finish_json(
200,
{"status": "ok", "data": public_user},
)

View File

@ -1,32 +1,29 @@
import json import json
import logging import logging
import os import os
import tornado.web
import tornado.escape
import bleach
import libgravatar
import requests
from app.classes.minecraft.serverjars import server_jar_obj from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty from app.classes.shared.helpers import Helpers
from app.classes.shared.helpers import helper from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.file_helpers import file_helper
from app.classes.web.base_handler import BaseHandler 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:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ServerHandler(BaseHandler):
class ServerHandler(BaseHandler):
@tornado.web.authenticated @tornado.web.authenticated
def get(self, page): def get(self, page):
# pylint: disable=unused-variable (
api_key, token_data, exec_user = self.current_user api_key,
superuser = exec_user['superuser'] _token_data,
exec_user,
) = self.current_user
superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
@ -34,84 +31,123 @@ class ServerHandler(BaseHandler):
if superuser: if superuser:
defined_servers = self.controller.list_defined_servers() defined_servers = self.controller.list_defined_servers()
exec_user_role.add("Super User") exec_user_role.add("Super User")
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions() exec_user_crafty_permissions = (
self.controller.crafty_perms.list_defined_crafty_permissions()
)
list_roles = [] list_roles = []
for role in self.controller.roles.get_all_roles(): for role in self.controller.roles.get_all_roles():
list_roles.append(self.controller.roles.get_role(role.role_id)) list_roles.append(self.controller.roles.get_role(role.role_id))
else: else:
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user["user_id"]) exec_user_crafty_permissions = (
defined_servers = self.controller.servers.get_authorized_servers(exec_user["user_id"]) 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 = [] list_roles = []
for r in exec_user['roles']: for r in exec_user["roles"]:
role = self.controller.roles.get_role(r) role = self.controller.roles.get_role(r)
exec_user_role.add(role['role_name']) exec_user_role.add(role["role_name"])
list_roles.append(self.controller.roles.get_role(role['role_id'])) list_roles.append(self.controller.roles.get_role(role["role_id"]))
template = "public/404.html" template = "public/404.html"
page_data = { page_data = {
'version_data': helper.get_version_string(), "version_data": self.helper.get_version_string(),
'user_data': exec_user, "user_data": exec_user,
'user_role' : exec_user_role, "user_role": exec_user_role,
'roles' : list_roles, "roles": list_roles,
'user_crafty_permissions' : exec_user_crafty_permissions, "user_crafty_permissions": exec_user_crafty_permissions,
'crafty_permissions': { "crafty_permissions": {
'Server_Creation': Enum_Permissions_Crafty.Server_Creation, "Server_Creation": EnumPermissionsCrafty.SERVER_CREATION,
'User_Config': Enum_Permissions_Crafty.User_Config, "User_Config": EnumPermissionsCrafty.USER_CONFIG,
'Roles_Config': Enum_Permissions_Crafty.Roles_Config, "Roles_Config": EnumPermissionsCrafty.ROLES_CONFIG,
}, },
'server_stats': { "server_stats": {
'total': len(self.controller.list_defined_servers()), "total": len(self.controller.list_defined_servers()),
'running': len(self.controller.list_running_servers()), "running": len(self.controller.list_running_servers()),
'stopped': (len(self.controller.list_defined_servers()) - len(self.controller.list_running_servers())) "stopped": (
len(self.controller.list_defined_servers())
- len(self.controller.list_running_servers())
),
}, },
'hosts_data': self.controller.management.get_latest_hosts_stats(), "hosts_data": self.controller.management.get_latest_hosts_stats(),
'menu_servers': defined_servers, "menu_servers": defined_servers,
'show_contribute': helper.get_setting("show_contribute_link", True), "show_contribute": self.helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user["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"])), "lang_page": Helpers.get_lang_page(
'api_key': { self.controller.users.get_user_lang_by_id(exec_user["user_id"])
'name': api_key.name, ),
'created': api_key.created, "api_key": {
'server_permissions': api_key.server_permissions, "name": api_key.name,
'crafty_permissions': api_key.crafty_permissions, "created": api_key.created,
'superuser': api_key.superuser "server_permissions": api_key.server_permissions,
} if api_key is not None else None, "crafty_permissions": api_key.crafty_permissions,
'superuser': superuser "superuser": api_key.superuser,
}
if api_key is not None
else None,
"superuser": superuser,
} }
if helper.get_setting("allow_nsfw_profile_pictures"): if self.helper.get_setting("allow_nsfw_profile_pictures"):
rating = "x" rating = "x"
else: else:
rating = "g" rating = "g"
if exec_user["email"] != "default@example.com" or "":
if exec_user['email'] != 'default@example.com' or "": gravatar = libgravatar.Gravatar(
g = libgravatar.Gravatar(libgravatar.sanitize_email(exec_user['email'])) 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: url = gravatar.get_image(
profile_url = url size=80,
else: default="404",
force_default=False,
rating=rating,
filetype_extension=False,
use_ssl=True,
) # + "?d=404"
try:
if requests.head(url).status_code != 404:
profile_url = url
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
except:
profile_url = "/static/assets/images/faces-clipart/pic-3.png" profile_url = "/static/assets/images/faces-clipart/pic-3.png"
else: else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png" profile_url = "/static/assets/images/faces-clipart/pic-3.png"
page_data['user_image'] = profile_url page_data["user_image"] = profile_url
if superuser: if superuser:
page_data['roles'] = list_roles page_data["roles"] = list_roles
if page == "step1": if page == "step1":
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]): if not superuser and not self.controller.crafty_perms.can_create_server(
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached") exec_user["user_id"]
):
self.redirect(
"/panel/error?error=Unauthorized access: "
"not a server creator or server limit reached"
)
return return
page_data['server_types'] = server_jar_obj.get_serverjar_data() page_data["online"] = Helpers.check_internet()
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data()) page_data["server_types"] = self.controller.server_jars.get_serverjar_data()
page_data["js_server_types"] = json.dumps(
self.controller.server_jars.get_serverjar_data()
)
template = "server/wizard.html" template = "server/wizard.html"
if page == "bedrock_step1": if page == "bedrock_step1":
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]): if not superuser and not self.controller.crafty_perms.can_create_server(
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached") exec_user["user_id"]
):
self.redirect(
"/panel/error?error=Unauthorized access: "
"not a server creator or server limit reached"
)
return return
template = "server/bedrock_wizard.html" template = "server/bedrock_wizard.html"
@ -124,19 +160,20 @@ class ServerHandler(BaseHandler):
@tornado.web.authenticated @tornado.web.authenticated
def post(self, page): def post(self, page):
# pylint: disable=unused-variable api_key, _token_data, exec_user = self.current_user
api_key, token_data, exec_user = self.current_user superuser = exec_user["superuser"]
superuser = exec_user['superuser']
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
template = "public/404.html" template = "public/404.html"
page_data = { page_data = {
'version_data': "version_data_here", # TODO "version_data": "version_data_here", # TODO
'user_data': exec_user, "user_data": exec_user,
'show_contribute': helper.get_setting("show_contribute_link", True), "show_contribute": self.helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user["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"])) "lang_page": Helpers.get_lang_page(
self.controller.users.get_user_lang_by_id(exec_user["user_id"])
),
} }
if page == "command": if page == "command":
@ -145,53 +182,71 @@ class ServerHandler(BaseHandler):
if server_id is not None: if server_id is not None:
if command == "clone_server": if command == "clone_server":
def is_name_used(name): def is_name_used(name):
for server in self.controller.servers.get_all_defined_servers(): for server in self.controller.servers.get_all_defined_servers():
if server['server_name'] == name: if server["server_name"] == name:
return True return True
return return
server_data = self.controller.servers.get_server_data_by_id(server_id) server_data = self.controller.servers.get_server_data_by_id(
server_uuid = server_data.get('server_uuid') server_id
new_server_name = server_data.get('server_name') + " (Copy)" )
server_uuid = server_data.get("server_uuid")
new_server_name = server_data.get("server_name") + " (Copy)"
name_counter = 1 name_counter = 1
while is_name_used(new_server_name): while is_name_used(new_server_name):
name_counter += 1 name_counter += 1
new_server_name = server_data.get('server_name') + f" (Copy {name_counter})" new_server_name = (
server_data.get("server_name") + f" (Copy {name_counter})"
)
new_server_uuid = helper.create_uuid() new_server_uuid = Helpers.create_uuid()
while os.path.exists(os.path.join(helper.servers_dir, new_server_uuid)): while os.path.exists(
new_server_uuid = helper.create_uuid() os.path.join(self.helper.servers_dir, new_server_uuid)
new_server_path = os.path.join(helper.servers_dir, new_server_uuid) ):
new_server_uuid = Helpers.create_uuid()
new_server_path = os.path.join(
self.helper.servers_dir, new_server_uuid
)
# copy the old server # copy the old server
file_helper.copy_dir(server_data.get('path'), new_server_path) FileHelpers.copy_dir(server_data.get("path"), new_server_path)
# TODO get old server DB data to individual variables # TODO get old server DB data to individual variables
stop_command = server_data.get('stop_command') stop_command = server_data.get("stop_command")
new_server_command = str(server_data.get('execution_command')).replace(server_uuid, new_server_uuid) new_server_command = str(
new_executable = server_data.get('executable') server_data.get("execution_command")
new_server_log_file = str(helper.get_os_understandable_path(server_data.get('log_path'))).replace(server_uuid, new_server_uuid) ).replace(server_uuid, new_server_uuid)
server_port = server_data.get('server_port') new_executable = server_data.get("executable")
server_type = server_data.get('type') new_server_log_file = str(
Helpers.get_os_understandable_path(server_data.get("log_path"))
).replace(server_uuid, new_server_uuid)
backup_path = os.path.join(self.helper.backup_path, new_server_uuid)
server_port = server_data.get("server_port")
server_type = server_data.get("type")
self.controller.servers.create_server(new_server_name, self.controller.servers.create_server(
new_server_uuid, new_server_name,
new_server_path, new_server_uuid,
"", new_server_path,
new_server_command, backup_path,
new_executable, new_server_command,
new_server_log_file, new_executable,
stop_command, new_server_log_file,
server_type, stop_command,
server_port) server_type,
server_port,
)
self.controller.init_all_servers() self.controller.init_all_servers()
return return
self.controller.management.send_command(exec_user['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 page == "step1":
@ -199,82 +254,121 @@ class ServerHandler(BaseHandler):
user_roles = self.controller.roles.get_all_roles() user_roles = self.controller.roles.get_all_roles()
else: else:
user_roles = self.controller.roles.get_all_roles() user_roles = self.controller.roles.get_all_roles()
server = bleach.clean(self.get_argument('server', '')) server = bleach.clean(self.get_argument("server", ""))
server_name = bleach.clean(self.get_argument('server_name', '')) server_name = bleach.clean(self.get_argument("server_name", ""))
min_mem = bleach.clean(self.get_argument('min_memory', '')) min_mem = bleach.clean(self.get_argument("min_memory", ""))
max_mem = bleach.clean(self.get_argument('max_memory', '')) max_mem = bleach.clean(self.get_argument("max_memory", ""))
port = bleach.clean(self.get_argument('port', '')) port = bleach.clean(self.get_argument("port", ""))
import_type = bleach.clean(self.get_argument('create_type', '')) import_type = bleach.clean(self.get_argument("create_type", ""))
import_server_path = bleach.clean(self.get_argument('server_path', '')) import_server_path = bleach.clean(self.get_argument("server_path", ""))
import_server_jar = bleach.clean(self.get_argument('server_jar', '')) import_server_jar = bleach.clean(self.get_argument("server_jar", ""))
server_parts = server.split("|") server_parts = server.split("|")
captured_roles = [] captured_roles = []
for role in user_roles: for role in user_roles:
if bleach.clean(self.get_argument(str(role), '')) == "on": if bleach.clean(self.get_argument(str(role), "")) == "on":
captured_roles.append(role) captured_roles.append(role)
if not server_name: if not server_name:
self.redirect("/panel/error?error=Server name cannot be empty!") self.redirect("/panel/error?error=Server name cannot be empty!")
return return
if import_type == 'import_jar': if import_type == "import_jar":
good_path = self.controller.verify_jar_server(import_server_path, import_server_jar) good_path = self.controller.verify_jar_server(
import_server_path, import_server_jar
)
if not good_path: if not good_path:
self.redirect("/panel/error?error=Server path or Server Jar not found!") self.redirect(
"/panel/error?error=Server path or Server Jar not found!"
)
return return
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port) new_server_id = self.controller.import_jar_server(
self.controller.management.add_to_audit_log(exec_user['user_id'], server_name,
f"imported a jar server named \"{server_name}\"", # Example: Admin imported a server named "old creative" import_server_path,
new_server_id, import_server_jar,
self.get_remote_ip()) min_mem,
elif import_type == 'import_zip': max_mem,
port,
)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f'imported a jar server named "{server_name}"',
new_server_id,
self.get_remote_ip(),
)
elif import_type == "import_zip":
# here import_server_path means the zip path # here import_server_path means the zip path
zip_path = bleach.clean(self.get_argument('root_path')) zip_path = bleach.clean(self.get_argument("root_path"))
good_path = helper.check_path_exists(zip_path) good_path = Helpers.check_path_exists(zip_path)
if not good_path: if not good_path:
self.redirect("/panel/error?error=Temp path not found!") self.redirect("/panel/error?error=Temp path not found!")
return return
new_server_id = self.controller.import_zip_server(server_name, zip_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": if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with" + self.redirect(
f"sudo chown -R crafty:crafty {import_server_path} And sudo chmod 2775 -R {import_server_path}") f"/panel/error?error=Zip file not accessible! "
f"You can fix this permissions issue with "
f"sudo chown -R crafty:crafty {import_server_path} "
f"And sudo chmod 2775 -R {import_server_path}"
)
return return
self.controller.management.add_to_audit_log(exec_user['user_id'], self.controller.management.add_to_audit_log(
f"imported a zip server named \"{server_name}\"", # Example: Admin imported a server named "old creative" exec_user["user_id"],
new_server_id, f'imported a zip server named "{server_name}"',
self.get_remote_ip()) new_server_id,
#deletes temp dir self.get_remote_ip(),
file_helper.del_dirs(zip_path) )
# deletes temp dir
FileHelpers.del_dirs(zip_path)
else: else:
if len(server_parts) != 2: if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data") self.redirect("/panel/error?error=Invalid server data")
return return
server_type, server_version = server_parts server_type, server_version = server_parts
# TODO: add server type check here and call the correct server add functions if not a jar # TODO: add server type check here and call the correct server
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"]) # add functions if not a jar
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port) new_server_id = self.controller.create_jar_server(
self.controller.management.add_to_audit_log(exec_user['user_id'], server_type, server_version, server_name, min_mem, max_mem, port
f"created a {server_version} {str(server_type).capitalize()} server named \"{server_name}\"", )
# Example: Admin created a 1.16.5 Bukkit server named "survival" self.controller.management.add_to_audit_log(
new_server_id, exec_user["user_id"],
self.get_remote_ip()) f"created a {server_version} {str(server_type).capitalize()}"
f' 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 # 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 len(captured_roles) == 0:
if not superuser: if not superuser:
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid") new_server_uuid = self.controller.servers.get_server_data_by_id(
role_id = self.controller.roles.add_role(f"Creator of Server with uuid={new_server_uuid}") new_server_id
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111") ).get("server_uuid")
self.controller.users.add_role_to_user(exec_user["user_id"], role_id) role_id = self.controller.roles.add_role(
self.controller.crafty_perms.add_server_creation(exec_user["user_id"]) 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: else:
for role in captured_roles: for role in captured_roles:
role_id = role role_id = role
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111") self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111"
)
self.controller.stats.record_stats() self.controller.stats.record_stats()
self.redirect("/panel/dashboard") self.redirect("/panel/dashboard")
@ -284,80 +378,114 @@ class ServerHandler(BaseHandler):
user_roles = self.controller.roles.get_all_roles() user_roles = self.controller.roles.get_all_roles()
else: else:
user_roles = self.controller.roles.get_all_roles() user_roles = self.controller.roles.get_all_roles()
server = bleach.clean(self.get_argument('server', '')) server = bleach.clean(self.get_argument("server", ""))
server_name = bleach.clean(self.get_argument('server_name', '')) server_name = bleach.clean(self.get_argument("server_name", ""))
port = bleach.clean(self.get_argument('port', '')) port = bleach.clean(self.get_argument("port", ""))
import_type = bleach.clean(self.get_argument('create_type', '')) import_type = bleach.clean(self.get_argument("create_type", ""))
import_server_path = bleach.clean(self.get_argument('server_path', '')) import_server_path = bleach.clean(self.get_argument("server_path", ""))
import_server_exe = bleach.clean(self.get_argument('server_jar', '')) import_server_exe = bleach.clean(self.get_argument("server_jar", ""))
server_parts = server.split("|") server_parts = server.split("|")
captured_roles = [] captured_roles = []
for role in user_roles: for role in user_roles:
if bleach.clean(self.get_argument(str(role), '')) == "on": if bleach.clean(self.get_argument(str(role), "")) == "on":
captured_roles.append(role) captured_roles.append(role)
if not server_name: if not server_name:
self.redirect("/panel/error?error=Server name cannot be empty!") self.redirect("/panel/error?error=Server name cannot be empty!")
return return
if import_type == 'import_jar': if import_type == "import_jar":
good_path = self.controller.verify_jar_server(import_server_path, import_server_exe) good_path = self.controller.verify_jar_server(
import_server_path, import_server_exe
)
if not good_path: if not good_path:
self.redirect("/panel/error?error=Server path or Server Jar not found!") self.redirect(
"/panel/error?error=Server path or Server Jar not found!"
)
return return
new_server_id = self.controller.import_bedrock_server(server_name, import_server_path,import_server_exe, port) new_server_id = self.controller.import_bedrock_server(
self.controller.management.add_to_audit_log(exec_user['user_id'], server_name, import_server_path, import_server_exe, port
f"imported a jar server named \"{server_name}\"", # Example: Admin imported a server named "old creative" )
new_server_id, self.controller.management.add_to_audit_log(
self.get_remote_ip()) exec_user["user_id"],
elif import_type == 'import_zip': f'imported a jar server named "{server_name}"',
new_server_id,
self.get_remote_ip(),
)
elif import_type == "import_zip":
# here import_server_path means the zip path # here import_server_path means the zip path
zip_path = bleach.clean(self.get_argument('root_path')) zip_path = bleach.clean(self.get_argument("root_path"))
good_path = helper.check_path_exists(zip_path) good_path = Helpers.check_path_exists(zip_path)
if not good_path: if not good_path:
self.redirect("/panel/error?error=Temp path not found!") self.redirect("/panel/error?error=Temp path not found!")
return return
new_server_id = self.controller.import_bedrock_zip_server(server_name, zip_path, import_server_exe, port) new_server_id = self.controller.import_bedrock_zip_server(
server_name, zip_path, import_server_exe, port
)
if new_server_id == "false": if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with" + self.redirect(
f"sudo chown -R crafty:crafty {import_server_path} And sudo chmod 2775 -R {import_server_path}") f"/panel/error?error=Zip file not accessible! "
f"You can fix this permissions issue with"
f"sudo chown -R crafty:crafty {import_server_path} "
f"And sudo chmod 2775 -R {import_server_path}"
)
return return
self.controller.management.add_to_audit_log(exec_user['user_id'], self.controller.management.add_to_audit_log(
f"imported a zip server named \"{server_name}\"", # Example: Admin imported a server named "old creative" exec_user["user_id"],
new_server_id, f'imported a zip server named "{server_name}"',
self.get_remote_ip()) new_server_id,
#deletes temp dir self.get_remote_ip(),
file_helper.del_dirs(zip_path) )
# deletes temp dir
FileHelpers.del_dirs(zip_path)
else: else:
if len(server_parts) != 2: if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data") self.redirect("/panel/error?error=Invalid server data")
return return
server_type, server_version = server_parts server_type, server_version = server_parts
# TODO: add server type check here and call the correct server add functions if not a jar # TODO: add server type check here and call the correct server
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"]) # add functions if not a jar
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port) new_server_id = self.controller.create_jar_server(
self.controller.management.add_to_audit_log(exec_user['user_id'], server_type, server_version, server_name, min_mem, max_mem, port
f"created a {server_version} {str(server_type).capitalize()} server named \"{server_name}\"", )
# Example: Admin created a 1.16.5 Bukkit server named "survival" self.controller.management.add_to_audit_log(
new_server_id, exec_user["user_id"],
self.get_remote_ip()) f"created a {server_version} {str(server_type).capitalize()} "
f'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 # 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 len(captured_roles) == 0:
if not superuser: if not superuser:
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid") new_server_uuid = self.controller.servers.get_server_data_by_id(
role_id = self.controller.roles.add_role(f"Creator of Server with uuid={new_server_uuid}") new_server_id
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111") ).get("server_uuid")
self.controller.users.add_role_to_user(exec_user["user_id"], role_id) role_id = self.controller.roles.add_role(
self.controller.crafty_perms.add_server_creation(exec_user["user_id"]) 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: else:
for role in captured_roles: for role in captured_roles:
role_id = role role_id = role
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111") self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111"
)
self.controller.stats.record_stats() self.controller.stats.record_stats()
self.redirect("/panel/dashboard") self.redirect("/panel/dashboard")
@ -369,4 +497,4 @@ class ServerHandler(BaseHandler):
translate=self.translator.translate, translate=self.translator.translate,
) )
except RuntimeError: except RuntimeError:
self.redirect('/panel/dashboard') self.redirect("/panel/dashboard")

View File

@ -1,17 +1,24 @@
from typing import ( Optional ) from typing import Optional
try: try:
import tornado.web import tornado.web
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
from app.classes.shared.helpers import helper from app.classes.shared.helpers import helper
helper.auto_installer_fix(e) helper.auto_installer_fix(e)
class CustomStaticHandler(tornado.web.StaticFileHandler): class CustomStaticHandler(tornado.web.StaticFileHandler):
def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
try: try:
return super().validate_absolute_path(root, absolute_path) return super().validate_absolute_path(root, absolute_path)
except tornado.web.HTTPError as error: except tornado.web.HTTPError as error:
if 'HTTP 404: Not Found' in str(error): if "HTTP 404: Not Found" in str(error):
self.set_status(404) 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",
}
)

View File

@ -1,46 +1,53 @@
import logging import logging
from app.classes.shared.helpers import helper
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class StatusHandler(BaseHandler): class StatusHandler(BaseHandler):
def get(self): def get(self):
page_data = {} page_data = {}
page_data['lang'] = helper.get_setting('language') page_data["lang"] = self.helper.get_setting("language")
page_data['lang_page'] = helper.getLangPage(helper.get_setting('language')) page_data["lang_page"] = self.helper.get_lang_page(
page_data['servers'] = self.controller.servers.get_all_servers_stats() self.helper.get_setting("language")
)
page_data["servers"] = self.controller.servers.get_all_servers_stats()
running = 0 running = 0
for srv in page_data['servers']: for srv in page_data["servers"]:
if srv['stats']['running']: if srv["stats"]["running"]:
running += 1 running += 1
server_data = srv.get('server_data', False) server_data = srv.get("server_data", False)
server_id = server_data.get('server_id', False) server_id = server_data.get("server_id", False)
srv['raw_ping_result'] = self.controller.servers.get_server_stats_by_id(server_id) srv["raw_ping_result"] = self.controller.servers.get_server_stats_by_id(
if 'icon' not in srv['raw_ping_result']: server_id
srv['raw_ping_result']['icon'] = False )
if "icon" not in srv["raw_ping_result"]:
srv["raw_ping_result"]["icon"] = False
page_data['running'] = running page_data["running"] = running
template = 'public/status.html' template = "public/status.html"
self.render( self.render(
template, template,
data=page_data, data=page_data,
translate=self.translator.translate, translate=self.translator.translate,
) )
def post(self): def post(self):
page_data = {} page_data = {}
page_data['servers'] = self.controller.servers.get_all_servers_stats() page_data["servers"] = self.controller.servers.get_all_servers_stats()
for srv in page_data['servers']: for srv in page_data["servers"]:
server_data = srv.get('server_data', False) server_data = srv.get("server_data", False)
server_id = server_data.get('server_id', False) server_id = server_data.get("server_id", False)
srv['raw_ping_result'] = self.controller.servers.get_server_stats_by_id(server_id) srv["raw_ping_result"] = self.controller.servers.get_server_stats_by_id(
template = 'public/status.html' server_id
)
template = "public/status.html"
self.render( self.render(
template, template,
data=page_data, data=page_data,
translate=self.translator.translate, translate=self.translator.translate,
) )

View File

@ -3,43 +3,55 @@ import sys
import json import json
import asyncio import asyncio
import logging import logging
import tornado.web
import tornado.ioloop
import tornado.log
import tornado.template
import tornado.escape
import tornado.locale
import tornado.httpserver
from app.classes.shared.translation import translation from app.classes.shared.console import Console
from app.classes.shared.console import console from app.classes.shared.helpers import Helpers
from app.classes.shared.helpers import helper from app.classes.shared.main_controller import Controller
from app.classes.web.file_handler import FileHandler from app.classes.web.file_handler import FileHandler
from app.classes.web.public_handler import PublicHandler from app.classes.web.public_handler import PublicHandler
from app.classes.web.panel_handler import PanelHandler from app.classes.web.panel_handler import PanelHandler
from app.classes.web.default_handler import DefaultHandler from app.classes.web.default_handler import DefaultHandler
from app.classes.web.routes.api.api_handlers import api_handlers
from app.classes.web.server_handler import ServerHandler from app.classes.web.server_handler import ServerHandler
from app.classes.web.ajax_handler import AjaxHandler from app.classes.web.ajax_handler import AjaxHandler
from app.classes.web.api_handler import ServersStats, NodeStats from app.classes.web.api_handler import (
ServersStats,
NodeStats,
ServerBackup,
StartServer,
StopServer,
RestartServer,
CreateUser,
DeleteUser,
ListServers,
SendCommand,
)
from app.classes.web.websocket_handler import SocketHandler from app.classes.web.websocket_handler import SocketHandler
from app.classes.web.static_handler import CustomStaticHandler from app.classes.web.static_handler import CustomStaticHandler
from app.classes.web.upload_handler import UploadHandler from app.classes.web.upload_handler import UploadHandler
from app.classes.web.http_handler import HTTPHandler, HTTPHandlerPage from app.classes.web.http_handler import HTTPHandler, HTTPHandlerPage
from app.classes.web.status_handler import StatusHandler from app.classes.web.status_handler import StatusHandler
try:
import tornado.web
import tornado.ioloop
import tornado.log
import tornado.template
import tornado.escape
import tornado.locale
import tornado.httpserver
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Webserver:
def __init__(self, controller, tasks_manager): class Webserver:
controller: Controller
helper: Helpers
def __init__(self, helper, controller, tasks_manager):
self.ioloop = None self.ioloop = None
self.HTTP_Server = None self.http_server = None
self.HTTPS_Server = None self.https_server = None
self.helper = helper
self.controller = controller self.controller = controller
self.tasks_manager = tasks_manager self.tasks_manager = tasks_manager
self._asyncio_patch() self._asyncio_patch()
@ -48,12 +60,12 @@ class Webserver:
def log_function(handler): def log_function(handler):
info = { info = {
'Status_Code': handler.get_status(), "Status_Code": handler.get_status(),
'Method': handler.request.method, "Method": handler.request.method,
'URL': handler.request.uri, "URL": handler.request.uri,
'Remote_IP': handler.request.remote_ip, "Remote_IP": handler.request.remote_ip,
# pylint: disable=consider-using-f-string # pylint: disable=consider-using-f-string
'Elapsed_Time': '%.2fms' % (handler.request.request_time() * 1000) "Elapsed_Time": "%.2fms" % (handler.request.request_time() * 1000),
} }
tornado.log.access_log.info(json.dumps(info, indent=4)) tornado.log.access_log.info(json.dumps(info, indent=4))
@ -61,39 +73,47 @@ class Webserver:
@staticmethod @staticmethod
def _asyncio_patch(): def _asyncio_patch():
""" """
As of Python 3.8 (on Windows), the asyncio default event handler has changed to "proactor", As of Python 3.8 (on Windows),
the asyncio default event handler has changed to "proactor",
where tornado expects the "selector" handler. where tornado expects the "selector" handler.
This function checks if the platform is windows and changes the event handler to suit. This function checks if the platform is windows and
changes the event handler to suit.
(Taken from https://github.com/mkdocs/mkdocs/commit/cf2b136d4257787c0de51eba2d9e30ded5245b31) (Taken from
https://github.com/mkdocs/mkdocs/commit/cf2b136d4257787c0de51eba2d9e30ded5245b31)
""" """
logger.debug("Checking if asyncio patch is required") logger.debug("Checking if asyncio patch is required")
if sys.platform.startswith("win") and sys.version_info >= (3, 8): if sys.platform.startswith("win") and sys.version_info >= (3, 8):
# pylint: disable=reimported,import-outside-toplevel,redefined-outer-name # pylint: disable=reimported,import-outside-toplevel,redefined-outer-name
import asyncio import asyncio
try: try:
from asyncio import WindowsSelectorEventLoopPolicy from asyncio import WindowsSelectorEventLoopPolicy
except ImportError: except ImportError:
logger.debug("asyncio patch isn't required") # 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: else:
if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy): if not isinstance(
asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy
):
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
logger.debug("Applied asyncio patch") logger.debug("Applied asyncio patch")
def run_tornado(self): def run_tornado(self):
# let's verify we have an SSL cert # let's verify we have an SSL cert
helper.create_self_signed_cert() self.helper.create_self_signed_cert()
http_port = helper.get_setting('http_port') http_port = self.helper.get_setting("http_port")
https_port = helper.get_setting('https_port') https_port = self.helper.get_setting("https_port")
debug_errors = helper.get_setting('show_errors') debug_errors = self.helper.get_setting("show_errors")
cookie_secret = helper.get_setting('cookie_secret') cookie_secret = self.helper.get_setting("cookie_secret")
if cookie_secret is False: if cookie_secret is False:
cookie_secret = helper.random_string_generator(32) cookie_secret = self.helper.random_string_generator(32)
if not http_port: if not http_port:
http_port = 8000 http_port = 8000
@ -102,38 +122,58 @@ class Webserver:
https_port = 8443 https_port = 8443
cert_objects = { cert_objects = {
'certfile': os.path.join(helper.config_dir, 'web', 'certs', 'commander.cert.pem'), "certfile": os.path.join(
'keyfile': os.path.join(helper.config_dir, 'web', 'certs', 'commander.key.pem'), self.helper.config_dir, "web", "certs", "commander.cert.pem"
),
"keyfile": os.path.join(
self.helper.config_dir, "web", "certs", "commander.key.pem"
),
} }
logger.info(f"Starting Web Server on ports http:{http_port} https:{https_port}") logger.info(f"Starting Web Server on ports http:{http_port} https:{https_port}")
asyncio.set_event_loop(asyncio.new_event_loop()) asyncio.set_event_loop(asyncio.new_event_loop())
tornado.template.Loader('.') tornado.template.Loader(".")
# TODO: Remove because we don't and won't use # TODO: Remove because we don't and won't use
tornado.locale.set_default_locale('en_EN') tornado.locale.set_default_locale("en_EN")
handler_args = {"controller": self.controller, "tasks_manager": self.tasks_manager, "translator": translation} handler_args = {
"helper": self.helper,
"controller": self.controller,
"tasks_manager": self.tasks_manager,
"translator": self.helper.translation,
}
handlers = [ handlers = [
(r'/', DefaultHandler, handler_args), (r"/", DefaultHandler, handler_args),
(r'/public/(.*)', PublicHandler, handler_args), (r"/public/(.*)", PublicHandler, handler_args),
(r'/panel/(.*)', PanelHandler, handler_args), (r"/panel/(.*)", PanelHandler, handler_args),
(r'/server/(.*)', ServerHandler, handler_args), (r"/server/(.*)", ServerHandler, handler_args),
(r'/ajax/(.*)', AjaxHandler, handler_args), (r"/ajax/(.*)", AjaxHandler, handler_args),
(r'/files/(.*)', FileHandler, handler_args), (r"/files/(.*)", FileHandler, handler_args),
(r'/api/stats/servers', ServersStats, handler_args), (r"/ws", SocketHandler, handler_args),
(r'/api/stats/node', NodeStats, handler_args), (r"/upload", UploadHandler, handler_args),
(r'/ws', SocketHandler, handler_args), (r"/status", StatusHandler, handler_args),
(r'/upload', UploadHandler, handler_args), # API Routes V1
(r'/status', StatusHandler, handler_args) (r"/api/v1/stats/servers", ServersStats, handler_args),
] (r"/api/v1/stats/node", NodeStats, handler_args),
(r"/api/v1/server/send_command", SendCommand, handler_args),
(r"/api/v1/server/backup", ServerBackup, handler_args),
(r"/api/v1/server/start", StartServer, handler_args),
(r"/api/v1/server/stop", StopServer, handler_args),
(r"/api/v1/server/restart", RestartServer, handler_args),
(r"/api/v1/list_servers", ListServers, handler_args),
(r"/api/v1/users/create_user", CreateUser, handler_args),
(r"/api/v1/users/delete_user", DeleteUser, handler_args),
# API Routes V2
*api_handlers(handler_args),
]
app = tornado.web.Application( app = tornado.web.Application(
handlers, handlers,
template_path=os.path.join(helper.webroot, 'templates'), template_path=os.path.join(self.helper.webroot, "templates"),
static_path=os.path.join(helper.webroot, 'static'), static_path=os.path.join(self.helper.webroot, "static"),
debug=debug_errors, debug=debug_errors,
cookie_secret=cookie_secret, cookie_secret=cookie_secret,
xsrf_cookies=True, xsrf_cookies=True,
@ -144,48 +184,56 @@ class Webserver:
static_handler_class=CustomStaticHandler, static_handler_class=CustomStaticHandler,
serve_traceback=debug_errors, serve_traceback=debug_errors,
) )
HTTPhanders = [(r'/', HTTPHandler, handler_args), http_handers = [
(r'/public/(.*)', HTTPHandlerPage, handler_args), (r"/", HTTPHandler, handler_args),
(r'/panel/(.*)', HTTPHandlerPage, handler_args), (r"/public/(.*)", HTTPHandlerPage, handler_args),
(r'/server/(.*)', HTTPHandlerPage, handler_args), (r"/panel/(.*)", HTTPHandlerPage, handler_args),
(r'/ajax/(.*)', HTTPHandlerPage, handler_args), (r"/server/(.*)", HTTPHandlerPage, handler_args),
(r'/api/stats/servers', HTTPHandlerPage, handler_args), (r"/ajax/(.*)", HTTPHandlerPage, handler_args),
(r'/api/stats/node', HTTPHandlerPage, handler_args), (r"/api/stats/servers", HTTPHandlerPage, handler_args),
(r'/ws', HTTPHandlerPage, handler_args), (r"/api/stats/node", HTTPHandlerPage, handler_args),
(r'/upload', HTTPHandlerPage, handler_args)] (r"/ws", HTTPHandlerPage, handler_args),
HTTPapp = tornado.web.Application( (r"/upload", HTTPHandlerPage, handler_args),
HTTPhanders, ]
template_path=os.path.join(helper.webroot, 'templates'), http_app = tornado.web.Application(
static_path=os.path.join(helper.webroot, 'static'), http_handers,
template_path=os.path.join(self.helper.webroot, "templates"),
static_path=os.path.join(self.helper.webroot, "static"),
debug=debug_errors, debug=debug_errors,
cookie_secret=cookie_secret, cookie_secret=cookie_secret,
xsrf_cookies=True, xsrf_cookies=True,
autoreload=False, autoreload=False,
log_function=self.log_function, log_function=self.log_function,
default_handler_class = HTTPHandler, default_handler_class=HTTPHandler,
login_url="/login", login_url="/login",
serve_traceback=debug_errors, serve_traceback=debug_errors,
) )
self.HTTP_Server = tornado.httpserver.HTTPServer(HTTPapp) self.http_server = tornado.httpserver.HTTPServer(http_app)
self.HTTP_Server.listen(http_port) self.http_server.listen(http_port)
self.HTTPS_Server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects) self.https_server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects)
self.HTTPS_Server.listen(https_port) self.https_server.listen(https_port)
logger.info(f"https://{helper.get_local_ip()}:{https_port} is up and ready for connections.") logger.info(
console.info(f"https://{helper.get_local_ip()}:{https_port} is up and ready for connections.") f"https://{Helpers.get_local_ip()}:{https_port} "
f"is up and ready for connections."
)
Console.info(
f"https://{Helpers.get_local_ip()}:{https_port} "
f"is up and ready for connections."
)
console.info("Server Init Complete: Listening For Connections:") Console.info("Server Init Complete: Listening For Connections:")
self.ioloop = tornado.ioloop.IOLoop.current() self.ioloop = tornado.ioloop.IOLoop.current()
self.ioloop.start() self.ioloop.start()
def stop_web_server(self): def stop_web_server(self):
logger.info("Shutting Down Web Server") logger.info("Shutting Down Web Server")
console.info("Shutting Down Web Server") Console.info("Shutting Down Web Server")
self.ioloop.stop() self.ioloop.stop()
self.HTTP_Server.stop() self.http_server.stop()
self.HTTPS_Server.stop() self.https_server.stop()
logger.info("Web Server Stopped") logger.info("Web Server Stopped")
console.info("Web Server Stopped") Console.info("Web Server Stopped")

View File

@ -1,79 +1,133 @@
import logging import logging
import os import os
import time import time
import tornado.web
import tornado.options
import tornado.httpserver
from app.classes.models.server_permissions import Enum_Permissions_Server from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.shared.helpers import helper from app.classes.shared.console import Console
from app.classes.shared.console import console from app.classes.shared.helpers import Helpers
from app.classes.shared.main_controller import Controller 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 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__) logger = logging.getLogger(__name__)
# Class & Function Defination
MAX_STREAMED_SIZE = 1024 * 1024 * 1024
@tornado.web.stream_request_body @tornado.web.stream_request_body
class UploadHandler(BaseHandler): class UploadHandler(BaseHandler):
# noinspection PyAttributeOutsideInit # noinspection PyAttributeOutsideInit
def initialize(self, controller: Controller=None, tasks_manager=None, translator=None): def initialize(
self,
helper: Helpers = None,
controller: Controller = None,
tasks_manager=None,
translator=None,
):
self.helper = helper
self.controller = controller self.controller = controller
self.tasks_manager = tasks_manager self.tasks_manager = tasks_manager
self.translator = translator self.translator = translator
def prepare(self): def prepare(self):
self.do_upload = True # Class & Function Defination
# pylint: disable=unused-variable api_key, _token_data, exec_user = self.current_user
api_key, token_data, exec_user = self.current_user server_id = self.get_argument("server_id", None)
server_id = self.get_argument('server_id', None) superuser = exec_user["superuser"]
superuser = exec_user['superuser']
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.superuser
user_id = exec_user['user_id'] user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB")
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
self.content_len = int(self.request.headers.get("Content-Length"))
if self.content_len > max_streamed_size:
logger.error(
f"User with ID {user_id} attempted to upload a file that"
f" exceeded the max body size."
)
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"fileTooLarge",
self.controller.users.get_user_lang_by_id(user_id),
),
},
)
return
self.do_upload = True
if superuser: if superuser:
exec_user_server_permissions = self.controller.server_perms.list_defined_permissions() exec_user_server_permissions = (
self.controller.server_perms.list_defined_permissions()
)
elif api_key is not None: elif api_key is not None:
exec_user_server_permissions = self.controller.server_perms.get_api_key_permissions_list(api_key, server_id) exec_user_server_permissions = (
self.controller.server_perms.get_api_key_permissions_list(
api_key, server_id
)
)
else: else:
exec_user_server_permissions = self.controller.server_perms.get_user_id_permissions_list( exec_user_server_permissions = (
exec_user["user_id"], server_id) self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
)
server_id = self.request.headers.get('X-ServerId', None) server_id = self.request.headers.get("X-ServerId", None)
if user_id is None: if user_id is None:
logger.warning('User ID not found in upload handler call') logger.warning("User ID not found in upload handler call")
console.warning('User ID not found in upload handler call') Console.warning("User ID not found in upload handler call")
self.do_upload = False self.do_upload = False
if server_id is None: if server_id is None:
logger.warning('Server ID not found in upload handler call') logger.warning("Server ID not found in upload handler call")
console.warning('Server ID not found in upload handler call') Console.warning("Server ID not found in upload handler call")
self.do_upload = False self.do_upload = False
if Enum_Permissions_Server.Files not in exec_user_server_permissions: if EnumPermissionsServer.FILES not in exec_user_server_permissions:
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!') logger.warning(
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!') f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
Console.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
self.do_upload = False self.do_upload = False
path = self.request.headers.get('X-Path', None) path = self.request.headers.get("X-Path", None)
filename = self.request.headers.get('X-FileName', None) filename = self.request.headers.get("X-FileName", None)
full_path = os.path.join(path, filename) full_path = os.path.join(path, filename)
if not helper.in_path(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path): if not Helpers.in_path(
print(user_id, server_id, helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path) Helpers.get_os_understandable_path(
logger.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!') self.controller.servers.get_server_data_by_id(server_id)["path"]
console.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!') ),
full_path,
):
print(
user_id,
server_id,
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
)
logger.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
Console.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
self.do_upload = False self.do_upload = False
if self.do_upload: if self.do_upload:
@ -83,23 +137,23 @@ class UploadHandler(BaseHandler):
logger.error(f"Upload failed with error: {e}") logger.error(f"Upload failed with error: {e}")
self.do_upload = False self.do_upload = False
# If max_body_size is not set, you cannot upload files > 100MB # If max_body_size is not set, you cannot upload files > 100MB
self.request.connection.set_max_body_size(MAX_STREAMED_SIZE) self.request.connection.set_max_body_size(max_streamed_size)
def post(self): def post(self):
logger.info("Upload completed") logger.info("Upload completed")
files_left = int(self.request.headers.get('X-Files-Left', None)) files_left = int(self.request.headers.get("X-Files-Left", None))
if self.do_upload: if self.do_upload:
time.sleep(5) time.sleep(5)
if files_left == 0: if files_left == 0:
websocket_helper.broadcast('close_upload_box', 'success') self.helper.websocket_helper.broadcast("close_upload_box", "success")
self.finish('success') # Nope, I'm sending "success" self.finish("success") # Nope, I'm sending "success"
self.f.close() self.f.close()
else: else:
time.sleep(5) time.sleep(5)
if files_left == 0: if files_left == 0:
websocket_helper.broadcast('close_upload_box', 'error') self.helper.websocket_helper.broadcast("close_upload_box", "error")
self.finish('error') self.finish("error")
def data_received(self, chunk): def data_received(self, chunk):
if self.do_upload: if self.do_upload:

View File

@ -2,19 +2,13 @@ import json
import logging import logging
import asyncio import asyncio
from urllib.parse import parse_qsl from urllib.parse import parse_qsl
import tornado.websocket
from app.classes.shared.authentication import authentication from app.classes.shared.helpers import Helpers
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
try:
import tornado.websocket
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SocketHandler(tornado.websocket.WebSocketHandler): class SocketHandler(tornado.websocket.WebSocketHandler):
page = None page = None
page_query_params = None page_query_params = None
@ -23,63 +17,81 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
translator = None translator = None
io_loop = None io_loop = None
def initialize(self, controller=None, tasks_manager=None, translator=None): def initialize(
self, helper=None, controller=None, tasks_manager=None, translator=None
):
self.helper = helper
self.controller = controller self.controller = controller
self.tasks_manager = tasks_manager self.tasks_manager = tasks_manager
self.translator = translator self.translator = translator
self.io_loop = tornado.ioloop.IOLoop.current() self.io_loop = tornado.ioloop.IOLoop.current()
def get_remote_ip(self): def get_remote_ip(self):
remote_ip = self.request.headers.get("X-Real-IP") or \ remote_ip = (
self.request.headers.get("X-Forwarded-For") or \ self.request.headers.get("X-Real-IP")
self.request.remote_ip or self.request.headers.get("X-Forwarded-For")
or self.request.remote_ip
)
return remote_ip return remote_ip
def get_user_id(self): def get_user_id(self):
_, _, user = authentication.check(self.get_cookie('token')) _, _, user = self.controller.authentication.check(self.get_cookie("token"))
return user['user_id'] return user["user_id"]
def check_auth(self): def check_auth(self):
return authentication.check_bool(self.get_cookie('token')) return self.controller.authentication.check_bool(self.get_cookie("token"))
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
def open(self): def open(self):
logger.debug('Checking WebSocket authentication') logger.debug("Checking WebSocket authentication")
if self.check_auth(): if self.check_auth():
self.handle() self.handle()
else: else:
websocket_helper.send_message(self, 'notification', 'Not authenticated for WebSocket connection') self.helper.websocket_helper.send_message(
self, "notification", "Not authenticated for WebSocket connection"
)
self.close() self.close()
self.controller.management.add_to_audit_log_raw('unknown', self.controller.management.add_to_audit_log_raw(
0, 0, "unknown",
'Someone tried to connect via WebSocket without proper authentication', 0,
self.get_remote_ip()) 0,
websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication') "Someone tried to connect via WebSocket without proper authentication",
logger.warning('Someone tried to connect via WebSocket without proper authentication') self.get_remote_ip(),
)
self.helper.websocket_helper.broadcast(
"notification",
"Someone tried to connect via WebSocket without proper authentication",
)
logger.warning(
"Someone tried to connect via WebSocket without proper authentication"
)
def handle(self): def handle(self):
self.page = self.get_query_argument('page') self.page = self.get_query_argument("page")
self.page_query_params = dict(parse_qsl(helper.remove_prefix( self.page_query_params = dict(
self.get_query_argument('page_query_params'), parse_qsl(
'?' Helpers.remove_prefix(self.get_query_argument("page_query_params"), "?")
))) )
websocket_helper.add_client(self) )
logger.debug('Opened WebSocket connection') self.helper.websocket_helper.add_client(self)
logger.debug("Opened WebSocket connection")
# pylint: disable=arguments-renamed # pylint: disable=arguments-renamed
@staticmethod @staticmethod
def on_message(raw_message): def on_message(raw_message):
logger.debug(f'Got message from WebSocket connection {raw_message}') logger.debug(f"Got message from WebSocket connection {raw_message}")
message = json.loads(raw_message) message = json.loads(raw_message)
logger.debug(f"Event Type: {message['event']}, Data: {message['data']}") logger.debug(f"Event Type: {message['event']}, Data: {message['data']}")
def on_close(self): def on_close(self):
websocket_helper.remove_client(self) self.helper.websocket_helper.remove_client(self)
logger.debug('Closed WebSocket connection') logger.debug("Closed WebSocket connection")
async def write_message_int(self, message): async def write_message_int(self, message):
self.write_message(message) self.write_message(message)
def write_message_helper(self, message): def write_message_helper(self, message):
asyncio.run_coroutine_threadsafe(self.write_message_int(message), self.io_loop.asyncio_loop) asyncio.run_coroutine_threadsafe(
self.write_message_int(message), self.io_loop.asyncio_loop
)

View File

@ -1,12 +1,14 @@
import json import json
import logging import logging
from app.classes.shared.console import console from app.classes.shared.console import Console
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class WebSocketHelper: class WebSocketHelper:
def __init__(self): def __init__(self, helper):
self.helper = helper
self.clients = set() self.clients = set()
def add_client(self, client): def add_client(self, client):
@ -15,19 +17,26 @@ class WebSocketHelper:
def remove_client(self, client): def remove_client(self, client):
self.clients.remove(client) self.clients.remove(client)
# pylint: disable=no-self-use def send_message(
def send_message(self, client, event_type: str, data): self, client, event_type: str, data
): # pylint: disable=no-self-use
if client.check_auth(): if client.check_auth():
message = str(json.dumps({'event': event_type, 'data': data})) message = str(json.dumps({"event": event_type, "data": data}))
client.write_message_helper(message) client.write_message_helper(message)
def broadcast(self, event_type: str, data): def broadcast(self, event_type: str, data):
logger.debug(f"Sending to {len(self.clients)} clients: {json.dumps({'event': event_type, 'data': data})}") logger.debug(
f"Sending to {len(self.clients)} clients: "
f"{json.dumps({'event': event_type, 'data': data})}"
)
for client in self.clients: for client in self.clients:
try: try:
self.send_message(client, event_type, data) self.send_message(client, event_type, data)
except Exception as e: except Exception as e:
logger.exception(f'Error caught while sending WebSocket message to {client.get_remote_ip()} {e}') logger.exception(
f"Error caught while sending WebSocket message to "
f"{client.get_remote_ip()} {e}"
)
def broadcast_page(self, page: str, event_type: str, data): def broadcast_page(self, page: str, event_type: str, data):
def filter_fn(client): def filter_fn(client):
@ -51,7 +60,9 @@ class WebSocketHelper:
self.broadcast_with_fn(filter_fn, event_type, data) 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 broadcast_user_page_params(
self, page: str, params: dict, user_id: str, event_type: str, data
):
def filter_fn(client): def filter_fn(client):
if client.get_user_id() != user_id: if client.get_user_id() != user_id:
return False return False
@ -77,18 +88,22 @@ class WebSocketHelper:
def broadcast_with_fn(self, filter_fn, event_type: str, data): def broadcast_with_fn(self, filter_fn, event_type: str, data):
clients = list(filter(filter_fn, self.clients)) 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})}") logger.debug(
f"Sending to {len(clients)} out of {len(self.clients)} "
f"clients: {json.dumps({'event': event_type, 'data': data})}"
)
for client in clients: for client in clients:
try: try:
self.send_message(client, event_type, data) self.send_message(client, event_type, data)
except Exception as e: except Exception as e:
logger.exception(f'Error catched while sending WebSocket message to {client.get_remote_ip()} {e}') logger.exception(
f"Error catched while sending WebSocket message to "
f"{client.get_remote_ip()} {e}"
)
def disconnect_all(self): def disconnect_all(self):
console.info('Disconnecting WebSocket clients') Console.info("Disconnecting WebSocket clients")
for client in self.clients: for client in self.clients:
client.close() client.close()
console.info('Disconnected WebSocket clients') Console.info("Disconnected WebSocket clients")
websocket_helper = WebSocketHelper()

View File

@ -1,5 +1,4 @@
{ {
"https": true,
"http_port": 8000, "http_port": 8000,
"https_port": 8443, "https_port": 8443,
"language": "en_EN", "language": "en_EN",
@ -14,7 +13,15 @@
"virtual_terminal_lines": 70, "virtual_terminal_lines": 70,
"max_log_lines": 700, "max_log_lines": 700,
"max_audit_entries": 300, "max_audit_entries": 300,
"disabled_language_files": ["lol_EN.json", ""], "disabled_language_files": [
"keywords": ["help", "chunk"], "lol_EN.json",
"allow_nsfw_profile_pictures": false ""
],
"stream_size_GB": 1,
"keywords": [
"help",
"chunk"
],
"allow_nsfw_profile_pictures": false,
"enable_user_self_delete": false
} }

View File

@ -1,6 +1,6 @@
{ {
"major": 4, "major": 4,
"minor": 0, "minor": 0,
"sub": 0, "sub": 0,
"meta": "alpha.3.5" "meta": "beta"
} }

View File

@ -0,0 +1,178 @@
/*! jQuery UI - v1.13.1 - 2022-03-14
* http://jqueryui.com
* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css
* Copyright jQuery Foundation and other contributors; Licensed MIT */
.ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
-ms-filter: "alpha(opacity=0)"; /* support: IE8 */
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
pointer-events: none;
}
/* Icons
----------------------------------*/
.ui-icon {
display: inline-block;
vertical-align: middle;
margin-top: -.25em;
position: relative;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
.ui-widget-icon-block {
left: 50%;
margin-left: -8px;
display: block;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-resizable {
position: relative;
}
.ui-resizable-handle {
position: absolute;
font-size: 0.1px;
display: block;
-ms-touch-action: none;
touch-action: none;
}
.ui-resizable-disabled .ui-resizable-handle,
.ui-resizable-autohide .ui-resizable-handle {
display: none;
}
.ui-resizable-n {
cursor: n-resize;
height: 7px;
width: 100%;
top: -5px;
left: 0;
}
.ui-resizable-s {
cursor: s-resize;
height: 7px;
width: 100%;
bottom: -5px;
left: 0;
}
.ui-resizable-e {
cursor: e-resize;
width: 7px;
right: -5px;
top: 0;
height: 100%;
}
.ui-resizable-w {
cursor: w-resize;
width: 7px;
left: -5px;
top: 0;
height: 100%;
}
.ui-resizable-se {
cursor: se-resize;
width: 12px;
height: 12px;
right: 1px;
bottom: 1px;
}
.ui-resizable-sw {
cursor: sw-resize;
width: 9px;
height: 9px;
left: -5px;
bottom: -5px;
}
.ui-resizable-nw {
cursor: nw-resize;
width: 9px;
height: 9px;
left: -5px;
top: -5px;
}
.ui-resizable-ne {
cursor: ne-resize;
width: 9px;
height: 9px;
right: -5px;
top: -5px;
}
.ui-selectable {
-ms-touch-action: none;
touch-action: none;
}
.ui-selectable-helper {
position: absolute;
z-index: 100;
border: 1px dotted black;
}
.ui-sortable-handle {
-ms-touch-action: none;
touch-action: none;
}

View File

@ -0,0 +1,173 @@
.ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
-ms-filter: "alpha(opacity=0)"; /* support: IE8 */
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
pointer-events: none;
}
/* Icons
----------------------------------*/
.ui-icon {
display: inline-block;
vertical-align: middle;
margin-top: -.25em;
position: relative;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
.ui-widget-icon-block {
left: 50%;
margin-left: -8px;
display: block;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-resizable {
position: relative;
}
.ui-resizable-handle {
position: absolute;
font-size: 0.1px;
display: block;
-ms-touch-action: none;
touch-action: none;
}
.ui-resizable-disabled .ui-resizable-handle,
.ui-resizable-autohide .ui-resizable-handle {
display: none;
}
.ui-resizable-n {
cursor: n-resize;
height: 7px;
width: 100%;
top: -5px;
left: 0;
}
.ui-resizable-s {
cursor: s-resize;
height: 7px;
width: 100%;
bottom: -5px;
left: 0;
}
.ui-resizable-e {
cursor: e-resize;
width: 7px;
right: -5px;
top: 0;
height: 100%;
}
.ui-resizable-w {
cursor: w-resize;
width: 7px;
left: -5px;
top: 0;
height: 100%;
}
.ui-resizable-se {
cursor: se-resize;
width: 12px;
height: 12px;
right: 1px;
bottom: 1px;
}
.ui-resizable-sw {
cursor: sw-resize;
width: 9px;
height: 9px;
left: -5px;
bottom: -5px;
}
.ui-resizable-nw {
cursor: nw-resize;
width: 9px;
height: 9px;
left: -5px;
top: -5px;
}
.ui-resizable-ne {
cursor: ne-resize;
width: 9px;
height: 9px;
right: -5px;
top: -5px;
}
.ui-selectable {
-ms-touch-action: none;
touch-action: none;
}
.ui-selectable-helper {
position: absolute;
z-index: 100;
border: 1px dotted black;
}
.ui-sortable-handle {
-ms-touch-action: none;
touch-action: none;
}

9447
app/frontend/static/assets/js/jquery-ui.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -214,9 +214,14 @@
}; };
wsInternal.onerror = function (errorEvent) { wsInternal.onerror = function (errorEvent) {
console.error('WebSocket Error', errorEvent); console.error('WebSocket Error', errorEvent);
warn('WebSockets are required for Crafty to work. This websocket connection has been closed. Are you using a reverse proxy? See our'+
' documentation for details')
}; };
wsInternal.onclose = function (closeEvent) { wsInternal.onclose = function (closeEvent) {
console.log('Closed WebSocket', closeEvent); console.log('Closed WebSocket', closeEvent);
warn('WebSockets are required for Crafty to work. This websocket connection has been closed. Are you using a reverse proxy? See our'+
' documentation for details')
}; };
@ -262,6 +267,17 @@
}) })
}); });
} }
if (webSocket) {
webSocket.on('support_status_update', function (logs) {
if(logs.percent >= 100){
document.getElementById('logs_progress_bar').innerHTML = '100%';
document.getElementById('logs_progress_bar').style.width = '100%';
}else{
document.getElementById('logs_progress_bar').innerHTML = logs.percent +'%';
document.getElementById('logs_progress_bar').style.width = logs.percent + '%';
}
});
}
if (webSocket) { if (webSocket) {
webSocket.on('send_logs_bootbox', function (server_id) { webSocket.on('send_logs_bootbox', function (server_id) {
var x = document.querySelector('.bootbox'); var x = document.querySelector('.bootbox');

View File

@ -32,7 +32,14 @@
{% end %} {% end %}
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p> <p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
</div> </div>
{% if data['user_data']['preparing'] %}
<span class="dropdown-item" id="support_progress"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}<br><br></span>
<span class="dropdown-item" id="support_progress"><div class="support_progress" style="height: 15px; width: 100%;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="logs_progress_bar" role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
</div></span>
{% else %}
<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> <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>
{% end %}
{% if data['superuser'] %} {% 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> <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 %} {% end %}

View File

@ -9,7 +9,7 @@
<div class="content-wrapper"> <div class="content-wrapper">
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
@ -24,35 +24,39 @@
<div class="card"> <div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-history"></i> &nbsp;Audit Logs</h4> <h4 class="card-title"><i class="fas fa-history"></i> &nbsp;Audit Logs</h4>
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span> {% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%"> <table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%">
<thead> <thead>
<tr class="rounded"> <tr class="rounded">
<td>Username</td> <td>Username</td>
<td>Time</td> <td>Time</td>
<td>Action</td> <td>Action</td>
<td>Server ID</td> <td>Server ID</td>
<td>IP</td> <td>IP</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in data['audit_logs'] %} {% for row in data['audit_logs'] %}
<tr> <tr>
<td>{{ row['user_name'] }}</td> <td>{{ row['user_name'] }}</td>
<td> <td>
{{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }} {{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }}
</td> </td>
<td>{{ row['log_msg'] }}</td> <td>{{ row['log_msg'] }}</td>
<td>{{ row['server_id'] }}</td> <td>{{ row['server_id'] }}</td>
<td>{{ row['source_ip'] }}</td> <td>{{ row['source_ip'] }}</td>
</tr> </tr>
{% end %} {% end %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
@ -60,9 +64,10 @@
</div> </div>
</div> </div>
<style> <style>
.popover-body{ .popover-body {
color: white !important;; color: white !important;
} ;
}
</style> </style>
@ -76,37 +81,37 @@
{% block js %} {% block js %}
<script> <script>
$( document ).ready(function() { $(document).ready(function () {
console.log('ready for JS!') console.log('ready for JS!')
$('#audit_table').DataTable({ $('#audit_table').DataTable({
'order': [1, 'desc'] 'order': [1, 'desc']
} }
); );
}); });
</script> </script>
<script> <script>
$(document).ready(function(){ $(document).ready(function () {
$('[data-toggle="popover"]').popover(); $('[data-toggle="popover"]').popover();
if($(window).width() < 1000){ if ($(window).width() < 1000) {
$('.too_small').popover("show"); $('.too_small').popover("show");
} }
}); });
$(window).ready(function(){ $(window).ready(function () {
$('body').click(function(){ $('body').click(function () {
$('.too_small').popover("hide"); $('.too_small').popover("hide");
}); });
}); });
$(window).resize(function() { $(window).resize(function () {
// This will execute whenever the window is resized // This will execute whenever the window is resized
if($(window).width() < 1000){ if ($(window).width() < 1000) {
$('.too_small').popover("show"); $('.too_small').popover("show");
} }
else{ else {
$('.too_small').popover("hide"); $('.too_small').popover("hide");
} // New width } // New width
}); });
</script> </script>
{% end %} {% end %}

View File

@ -15,8 +15,9 @@
<div class="page-header"> <div class="page-header">
<h4 class="page-title">{{ translate('dashboard', 'dashboard', data['lang']) }} <h4 class="page-title">{{ translate('dashboard', 'dashboard', data['lang']) }}
{% if data['server_stats']['running'] != 0 %} {% if data['server_stats']['running'] != 0 %}
<span id="sync" style="margin-left: 5px;"><i class="fas fa-sync fa-spin"></i></span></h4> <span id="sync" style="margin-left: 5px;"><i class="fas fa-sync fa-spin"></i></span>
{% end %} </h4>
{% end %}
</div> </div>
</div> </div>
@ -38,12 +39,12 @@
</div> </div>
<div class="wrapper my-auto ml-auto ml-lg-4"> <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" <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') }}"> 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">{{ {{ translate('dashboard', 'cpuUsage', data['lang']) }}: <span id="cpu_usage">{{
data.get('hosts_data').get('cpu_usage') }}</span> data.get('hosts_data').get('cpu_usage') }}</span>
</p> </p>
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top" <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') }}"> title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}">
{{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{ {{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{
data.get('hosts_data').get('mem_percent') }}%</span> data.get('hosts_data').get('mem_percent') }}%</span>
</p> </p>
@ -74,7 +75,8 @@
<h3 class="mb-0 font-weight-semibold" id="total_players">{{ data['num_players'] }}</h3> <h3 class="mb-0 font-weight-semibold" id="total_players">{{ data['num_players'] }}</h3>
</div> </div>
<div class="wrapper my-auto ml-auto ml-lg-4"> <div class="wrapper my-auto ml-auto ml-lg-4">
<p class="mb-0 text-warning"><span id="max_players">0</span> {{ translate('dashboard', 'max', data['lang']) }}</p> <p class="mb-0 text-warning"><span id="max_players">0</span> {{ translate('dashboard', 'max',
data['lang']) }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -91,9 +93,11 @@
<h4 class="card-title"><i class="fas fa-server"></i> &nbsp;{{ translate('dashboard', 'allServers', <h4 class="card-title"><i class="fas fa-server"></i> &nbsp;{{ translate('dashboard', 'allServers',
data['lang']) }}</h4> data['lang']) }}</h4>
{% if len(data['servers']) > 0 %} {% if len(data['servers']) > 0 %}
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" , <span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span> data-placement="top"></span>
{% end %}
{% end %} {% end %}
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{ <div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('dashboard', 'newServer', data['lang']) }}</a></div> translate('dashboard', 'newServer', data['lang']) }}</a></div>
@ -111,21 +115,22 @@
{% end %} {% end %}
{% if len(data['servers']) > 0 %} {% if len(data['servers']) > 0 %}
<table class="table table-hover"> <!-- View for Large screen -->
<table id="servers_table" class="table table-hover d-none d-sm-table">
<thead> <thead>
<tr class="rounded"> <tr class="rounded" id="first" draggable="false">
<th>{{ translate('dashboard', 'server', data['lang']) }}</th> <th draggable="false">{{ translate('dashboard', 'server', data['lang']) }}</th>
<th>{{ translate('dashboard', 'actions', data['lang']) }}</th> <th draggable="false">{{ translate('dashboard', 'actions', data['lang']) }}</th>
<th>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</th> <th draggable="false">{{ translate('dashboard', 'cpuUsage', data['lang']) }}</th>
<th>{{ translate('dashboard', 'memUsage', data['lang']) }}</th> <th draggable="false">{{ translate('dashboard', 'memUsage', data['lang']) }}</th>
<th>{{ translate('dashboard', 'size', data['lang']) }}</th> <th draggable="false">{{ translate('dashboard', 'size', data['lang']) }}</th>
<th>{{ translate('dashboard', 'players', data['lang']) }}</th> <th draggable="false">{{ translate('dashboard', 'players', data['lang']) }}</th>
<th>{{ translate('dashboard', 'status', data['lang']) }}</th> <th draggable="false">{{ translate('dashboard', 'status', data['lang']) }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for server in data['servers'] %} {% for server in data['servers'] %}
<tr id="{{server['server_data']['server_id']}}" draggable="true" ondragstart="start()" ondragover="dragover()" ondragend="dragend()"> <tr id="{{server['server_data']['server_id']}}" draggable="true">
<td draggable="false"> <td draggable="false">
<i class="fas fa-server"></i> <i class="fas fa-server"></i>
<a draggable="false" href="/panel/server_detail?id={{server['server_data']['server_id']}}"> <a draggable="false" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
@ -133,21 +138,21 @@
</a> </a>
</td> </td>
<td id="controls{{server['server_data']['server_id']}}" class="actions_serverlist"> <td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% if server['user_command_permission'] %} {% if server['user_command_permission'] %}
{% if server['stats']['running'] %} {% if server['stats']['running'] %}
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" <a data-id="{{server['server_data']['server_id']}}" class="stop_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'stop' , data['lang']) }}"> data-toggle="tooltip" title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i> <i class="fas fa-stop"></i>
</a> &nbsp; </a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" <a data-id="{{server['server_data']['server_id']}}" class="restart_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'restart' , data['lang']) }}"> data-toggle="tooltip" title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i> <i class="fas fa-sync"></i>
</a> &nbsp; </a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" <a data-id="{{server['server_data']['server_id']}}" class="kill_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}"> data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i> <i class="fas fa-skull"></i>
</a> &nbsp; </a> &nbsp;
@ -160,29 +165,29 @@
<a data-id="{{server['server_data']['server_id']}}" class="" title="{{ <a data-id="{{server['server_data']['server_id']}}" class="" title="{{
translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting', translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting',
data['lang']) }}</i></a> data['lang']) }}</i></a>
{% elif server['stats']['downloading']%} {% elif server['stats']['downloading']%}
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'downloading', <a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'downloading',
data['lang']) }}</a> data['lang']) }}</a>
{% else %} {% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button" <a data-id="{{server['server_data']['server_id']}}" class="play_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'start' , data['lang']) }}"> data-toggle="tooltip" title="{{ translate('dashboard', 'start' , data['lang']) }}">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</a> &nbsp; </a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button" <a data-id="{{server['server_data']['server_id']}}" class="clone_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'clone' , data['lang']) }}"> data-toggle="tooltip" title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i> <i class="fas fa-clone"></i>
</a> &nbsp; </a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" <a data-id="{{server['server_data']['server_id']}}" class="kill_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}"> data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i> <i class="fas fa-skull"></i>
</a> &nbsp; </a> &nbsp;
{% end %} {% end %}
{% end %} {% end %}
</td> </td>
<td id="server_cpu_{{server['server_data']['server_id']}}"> <td draggable="false" id="server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" <div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}"> title="{{server['stats']['cpu']}}">
<div class="progress-bar <div class="progress-bar
{% if server['stats']['cpu'] <= 33 %} {% if server['stats']['cpu'] <= 33 %}
bg-success bg-success
@ -192,14 +197,14 @@
bg-danger bg-danger
{% end %} {% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" " role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div> aria-valuemin="0" aria-valuemax="100"></div>
</div> </div>
{{server['stats']['cpu']}}% {{server['stats']['cpu']}}%
</td> </td>
<td id="server_mem_{{server['server_data']['server_id']}}"> <td draggable="false" id="server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" <div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}"> title="{{server['stats']['mem']}}">
<div class="progress-bar <div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %} {% if server['stats']['mem_percent'] <= 33 %}
bg-success bg-success
@ -209,7 +214,7 @@
bg-danger bg-danger
{% end %} {% end %}
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0" " role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div> aria-valuemin="0" aria-valuemax="100"></div>
</div> </div>
{{server['stats']['mem_percent']}}% - {{server['stats']['mem_percent']}}% -
@ -219,16 +224,18 @@
{{server['stats']['mem']}} {{server['stats']['mem']}}
{% end %} {% end %}
</td> </td>
<td id="server_world_{{server['server_data']['server_id']}}"> <td draggable="false" id="server_world_{{server['server_data']['server_id']}}">
{{ server['stats']['world_size'] }} {{ server['stats']['world_size'] }}
</td> </td>
<td id="server_desc_{{server['server_data']['server_id']}}"> <td draggable="false" id="server_desc_{{server['server_data']['server_id']}}">
{% if server['stats']['int_ping_results'] %} {% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max', {{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
data['lang']) }} <br /> data['lang']) }} <br />
{% if server['stats']['desc'] != 'False' %} {% if server['stats']['desc'] != 'False' %}
{{ server['stats']['desc'] }} <br /> <div id="desc_id"
style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">{{
server['stats']['desc'] }}</div> <br />
{% end %} {% end %}
{% if server['stats']['version'] != 'False' %} {% if server['stats']['version'] != 'False' %}
@ -237,22 +244,180 @@
{% end %} {% end %}
</td> </td>
<td id="server_running_status_{{server['server_data']['server_id']}}"> <td draggable="false" id="server_running_status_{{server['server_data']['server_id']}}">
{% if server['stats']['running'] %} {% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', <span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span> data['lang']) }}</span>
{% elif server['stats']['crashed'] %} {% elif server['stats']['crashed'] %}
<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', <span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed',
data['lang']) }}</span> data['lang']) }}</span>
{% else %} {% else %}
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', <span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span> data['lang']) }}</span>
{% end %} {% end %}
</td> </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> <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> </tr>
{% end %} {% end %}
</tbody>
</table>
<!-- View for Small screen -->
<table id="servers_table" class="table table-hover d-table d-sm-none">
<thead>
<tr class="rounded" id="first" draggable="false">
<th scope="col" draggable="false">{{ translate('dashboard', 'server', data['lang']) }}</th>
<th scope="col" draggable="false">{{ translate('dashboard', 'actions', data['lang']) }}</th>
<th scope="col" draggable="false">{{ translate('dashboard', 'status', data['lang']) }}</th>
<th scope="col" draggable="false"></th>
</tr>
</thead>
<tbody>
{% for server in data['servers'] %}
<tr id="{{server['server_data']['server_id']}}" draggable="false">
<td scope="row"><i class="fas fa-server"></i>
<a draggable="false" href="/panel/server_detail?id={{server['server_data']['server_id']}}">
{{ server['server_data']['server_name'] }}
</a>
</td>
<td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% if server['user_command_permission'] %}
{% 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> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="restart_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% 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> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button"
data-toggle="tooltip" title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% end %}
{% end %}
</td>
<td draggable="false" id="m_server_running_status_{{server['server_data']['server_id']}}">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
{% elif server['stats']['crashed'] %}
<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed',
data['lang']) }}</span>
{% else %}
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span>
{% end %}
</td>
<td>
<span data-toggle="collapse" data-target="#details_{{server['server_data']['server_id']}}" aria-expanded="false" aria-controls="details_{{server['server_data']['server_id']}}"><i class="fas fa-chevron-down"></i></span>
</td>
</tr>
<tr id="details_{{server['server_data']['server_id']}}" class="collapse" draggable="false">
<td colspan="4">
<div class="collapse" id="details_{{server['server_data']['server_id']}}">
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</h6>
<div id="m_server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats']['cpu'] <= 33 %}
bg-success
{% elif 34 <= server['stats']['cpu'] <= 66 %}
bg-warning
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
</div>
</div>
<div class="col-6">
<h6>{{ translate('dashboard', 'memUsage', data['lang']) }}</h6>
<div draggable="false" id="m_server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
{% elif 34 <= server['stats']['mem_percent'] <= 66 %}
bg-warning
{% else %}
bg-danger
{% end %}
" 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
{% else %}
{{server['stats']['mem']}}
{% end %}
</div>
</div>
</div>
<br />
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'size', data['lang']) }}</h6>
<div draggable="false" id="m_server_world_{{server['server_data']['server_id']}}">
{{ server['stats']['world_size'] }}
</div>
</div>
<div class="col-6" style="width: auto;">
<h6>{{ translate('dashboard', 'players', data['lang']) }}</h6>
<div draggable="false" id="m_server_desc_{{server['server_data']['server_id']}}">
{% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
data['lang']) }} <br />
{% if server['stats']['desc'] != 'False' %}
<div id="desc_id" style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">{{ server['stats']['desc'] }}</div> <br />
{% end %}
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% end %}
</div>
</div>
</div>
</div>
</td>
</tr>
{% end %}
</tbody> </tbody>
</table> </table>
{% end %} {% end %}
@ -265,11 +430,25 @@
</div> </div>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
<div id="mobile"></div>
<style> <style>
.popover-body { .popover-body {
color: white !important; color: white !important;
; ;
} }
#desc_id {
-ms-overflow-style: none;
/* for Internet Explorer, Edge */
scrollbar-width: none;
/* for Firefox */
overflow-y: scroll;
}
#desc_id::-webkit-scrollbar {
display: none;
/* for Chrome, Safari, and Opera */
}
</style> </style>
@ -280,7 +459,7 @@
<script src="/static/assets/js/motd.js"></script> <script src="/static/assets/js/motd.js"></script>
<script> <script>
function display_motd() { function display_motd() {
var all_motds = Array.from(document.getElementsByClassName('input_motd')); let all_motds = Array.from(document.getElementsByClassName('input_motd'));
for (element of all_motds) { for (element of all_motds) {
initParser(element.id, element.id); initParser(element.id, element.id);
}; };
@ -291,13 +470,14 @@
if ($(window).width() < 1000) { if ($(window).width() < 1000) {
$('.too_small').popover("show"); $('.too_small').popover("show");
} }
}); });
$(window).ready(function () { $(window).ready(function () {
$('body').click(function () { $('body').click(function () {
$('.too_small').popover("hide"); $('.too_small').popover("hide");
}); });
}); });
$(window).resize(function () { $(window).resize(function () {
// This will execute whenever the window is resized // This will execute whenever the window is resized
if ($(window).width() < 1000) { if ($(window).width() < 1000) {
@ -312,7 +492,7 @@
function send_command(server_id, command) { function send_command(server_id, command) {
/* this getCookie function is in base.html */ /* this getCookie function is in base.html */
var token = getCookie("_xsrf"); const token = getCookie("_xsrf");
$.ajax({ $.ajax({
type: "POST", type: "POST",
@ -332,7 +512,7 @@
function send_kill(server_id) { function send_kill(server_id) {
/* this getCookie function is in base.html */ /* this getCookie function is in base.html */
var token = getCookie("_xsrf"); const token = getCookie("_xsrf");
$.ajax({ $.ajax({
type: "POST", type: "POST",
@ -349,6 +529,7 @@
} }
function update_one_server_status(server) { function update_one_server_status(server) {
/* Mobile view update */
server_cpu = document.getElementById('server_cpu_' + server.id); server_cpu = document.getElementById('server_cpu_' + server.id);
server_mem = document.getElementById('server_mem_' + server.id); server_mem = document.getElementById('server_mem_' + server.id);
server_world = document.getElementById('server_world_' + server.id); server_world = document.getElementById('server_world_' + server.id);
@ -357,71 +538,66 @@
server_players = document.getElementById('server_players_' + server.id); server_players = document.getElementById('server_players_' + server.id);
total_players = document.getElementById('total_players'); total_players = document.getElementById('total_players');
/* Mobile view update */
m_server_cpu = document.getElementById('m_server_cpu_' + server.id);
m_server_mem = document.getElementById('m_server_mem_' + server.id);
m_server_world = document.getElementById('m_server_world_' + server.id);
m_server_desc = document.getElementById('m_server_desc_' + server.id);
m_server_online_status = document.getElementById('m_server_running_status_' + server.id);
console.log("Received Data : " + server.id + ": " + server); console.log("Received Data : " + server.id + ": " + server);
/* TODO Update each element */ /* TODO Update each element */
/* Update CPU */ /* Update CPU */
cpu_status = ""; cpu_status = "bg-danger";
if (server.cpu <= 33) if (server.cpu <= 33) {
{
cpu_status = "bg-success"; cpu_status = "bg-success";
} } else if (server.cpu > 33 && server.cpu <= 66) {
else if (server.cpu > 33 && server.cpu <= 66)
{
cpu_status = "bg-warning"; cpu_status = "bg-warning";
} }
else
{ 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 + `%`;
cpu_status = "bg-danger"; m_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 + `%`;
}
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 +`%`;
/* Update Memory */ /* Update Memory */
mem_status = ""; mem_status = "bg-danger";
total_mem = ""; total_mem = "0 MB";
if (server.mem_percent <= 33)
{ if (server.mem_percent <= 33) {
mem_status = "bg-success"; mem_status = "bg-success";
} else if (server.mem_percent > 33 && server.mem_percent <= 66) } else if (server.mem_percent > 33 && server.mem_percent <= 66) {
{
mem_status = "bg-warning"; mem_status = "bg-warning";
} }
else
{
mem_status = "bg-danger";
}
if (server.mem == 0) if (server.mem !== 0) {
{
total_mem = "0 MB";
}
else
{
total_mem = server.mem; 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; 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;
m_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 */ /* Update World Infos */
server_world.innerHTML = server.world_size server_world.innerHTML = server.world_size
m_server_world.innerHTML = server.world_size
/* Update Server Infos */ /* Update Server Infos */
if (server.int_ping_results) { if (server.int_ping_results) {
/* Update Players */ /* Update Players */
if (server.players) { if (server.players) {
server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />` server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
m_server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
server_players.setAttribute('data-players', server.online); server_players.setAttribute('data-players', server.online);
server_players.setAttribute('data-max', server.max); server_players.setAttribute('data-max', server.max);
let servers = document.getElementsByClassName("server-player-totals"); let servers = document.getElementsByClassName("server-player-totals");
let all_total_players = 0; let all_total_players = 0;
let all_total_max_players = 0; let all_total_max_players = 0;
for(var i = 0; i < servers.length; i++){ for (let i = 0; i < servers.length; i++) {
try{ try {
all_total_players += parseInt(servers[i].getAttribute('data-players')); all_total_players += parseInt(servers[i].getAttribute('data-players'));
all_total_max_players += parseInt(servers[i].getAttribute('data-max')); all_total_max_players += parseInt(servers[i].getAttribute('data-max'));
}catch{ } catch {
console.log("Player totals are not of type int"); console.log("Player totals are not of type int");
} }
} }
@ -431,40 +607,47 @@
server_infos = ""; server_infos = "";
m_server_infos = "";
server_infos = server.online + " / " + server.max + " {{ translate('dashboard', 'max', data['lang']) }}<br />" server_infos = server.online + " / " + server.max + " {{ translate('dashboard', 'max', data['lang']) }}<br />"
} }
/* Update Motd */ /* Update Motd */
var motd = ""; let motd = "";
if (server.desc) { if (server.desc) {
motd = `<span id="input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span>`; motd = `<span id="input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span>`;
server_infos = server_infos + motd + "<br />"; m_server_infos = server_infos + '<div id="desc_id" style="word-wrap: break-word; overflow: auto;">' + motd + '</div>' + "<br />";
server_infos = server_infos + '<div id="desc_id" style="word-wrap: break-word; max-width: 85px !important; overflow: auto;">' + motd + '</div>' + "<br />";
} }
/* Version */ /* Version */
if (server.version) { if (server.version) {
server_infos = server_infos + server.version server_infos = server_infos + server.version
m_server_infos = m_server_infos + server.version
} }
server_desc.innerHTML = server_infos; server_desc.innerHTML = server_infos;
m_server_desc.innerHTML = m_server_infos;
} }
/* Update Online Status */ /* Update Online Status */
var online_status = ""; let online_status = `<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang'])}}</span>`;
if (server.running) { if (server.running) {
online_status = `<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])}}</span>`; online_status = `<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])}}</span>`;
} }
else {
if (server.crashed){ if (server.crashed) {
online_status = `<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang'])}}</span>` 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; server_online_status.innerHTML = online_status;
} }
function update_servers_status(data) { function update_servers_status(data) {
update_one_server_status(data[0]); try {
update_one_server_status(data[0]);
} catch (e) {
console.log('Failed to update server stats', e)
}
display_motd(); display_motd();
} }
@ -518,7 +701,7 @@
callback: function (result) { callback: function (result) {
if (result) { if (result) {
send_kill(server_id); send_kill(server_id);
var dialog = bootbox.dialog({ let dialog = bootbox.dialog({
title: '{% raw translate("dashboard", "killing", data["lang"]) %}', title: '{% raw translate("dashboard", "killing", data["lang"]) %}',
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>' message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
}); });
@ -540,7 +723,7 @@
webSocket.on('update_host_stats', function (hostStats) { 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}`; let 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_data.setAttribute('data-original-title', cpuDataTitle);
cpu_usage.textContent = hostStats.cpu_usage; cpu_usage.textContent = hostStats.cpu_usage;
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`); mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
@ -577,63 +760,117 @@
$(".clone_button").click(function () { $(".clone_button").click(function () {
server_id = $(this).attr("data-id"); server_id = $(this).attr("data-id");
send_command(server_id, 'clone_server'); send_command(server_id, 'clone_server');
bootbox.alert({ bootbox.dialog({
backdrop: true, backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}', title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>' message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
closeButton: false,
}); });
setTimeout(function () {
location.reload();
}, 5000)
}); });
}); });
</script> </script>
<script> <script src="/static/assets/js/jquery-ui.js"></script>
var row; <link rel="stylesheet" href="/static/assets/css/jquery-ui.css">
<link rel="stylesheet" href="/static/assets/css/jquery-ui.structure.css">
function start(){ <style>
row = event.target; @media only screen and (max-width: 760px) {
} #mobile {
function dragover(){ display: none;
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") .ui-sortable-handle {
$.ajax({ cursor: default;
type: "POST", padding: 2px;
headers: {'X-XSRFToken': token}, }
url: '/ajax/send_order?order='+id_string,
data: { .ui-sortable-handle:hover {
order: id_string, cursor: grab !important;
}, padding: 2px;
success: function(data){ }
console.log("got response:"); </style>
console.log(data); <script>
},
/* Search Bar */
$(document).ready(function () {
let servers_table = $('#servers_table').DataTable({
"ordering": false, // false to disable sorting (or any other option)
"paging": false
}); });
} document.getElementById('first').setAttribute('draggable', false);
$('.dataTables_length').addClass('bs-select');
});
/* Search Bar End */
$(document).on("mousedown keyup click", function (event) {
const value = $('.dataTables_filter input').val();
const is_mobile = $('#mobile').css('display') === 'none';
if ($("table#servers_table tbody").sortable("toArray").length > 1 && value === '' && !is_mobile) {
$("table#servers_table tbody").sortable("enable");
} else {
$("table#servers_table tbody").sortable("disable");
}
});
$(document).ready(function () {
function sendOrder(id_string) {
const 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);
},
});
}
// Inits the sortable
$("table#servers_table tbody")
.sortable({
items: '> tr',
cursor: "grabbing",
axis: "y",
revert: true,
handle: "i.fas.fa-server,a",
forcePlaceholderSize: true,
placeholder: 'table-placeholder',
deactivate: function (event, ui) {
// Gets the list of ids in the server list as an array,
// and joins the elements with an [,] and sends to the server.
const ids = $("table#servers_table tbody").sortable("toArray").join();
try {
sendOrder(ids);
} catch {
console.log("Search is actively supressing order change")
}
},
});
// Give the table an actual width so the draggable does't collapse when dragged
$("table#servers_table")
.children()
.find("td")
.each(function () {
$(this).css("width", "").css("width", $(this).outerWidth());
});
// Fixes the appearance of a scrollbar when a user tries to drag an item too low
// Disabled on mobile since sorting is disabled on mobile
if ($('#mobile').css('display') !== 'none') {
$("div#servers_table_wrapper").css({ overflow: "hidden" });
}
});
</script> </script>

View File

@ -9,11 +9,11 @@
<div class="content-wrapper"> <div class="content-wrapper">
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<!-- TODO: Translate the following --> <!-- TODO: Translate the following -->
<h4 class="page-title">{{ translate('panelConfig', 'pageTitle', data['lang']) }}</h4> <h4 class="page-title">{{ translate('panelConfig', 'pageTitle', data['lang']) }}</h4>
</div> </div>
</div> </div>
@ -30,11 +30,16 @@
<div class="col-md-12 col-lg-12 grid-margin stretch-card"> <div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang']) }}</h4> <h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang'])
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span> }}</h4>
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
<!-- TODO: Translate the following --> <!-- TODO: Translate the following -->
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{ translate('panelConfig', 'newUser', data['lang']) }}</a></div> <div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('panelConfig', 'newUser', data['lang']) }}</a></div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
@ -50,38 +55,38 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in data['users'] %} {% for user in data['users'] %}
<tr> <tr>
<td><i class="fas fa-user"></i> {{ user.username }}</td> <td><i class="fas fa-user"></i> {{ user.username }}</td>
<td> <td>
{% if user.enabled %} {% if user.enabled %}
<span class="text-success"> <span class="text-success">
<i class="fas fa-check-square"></i> Yes <i class="fas fa-check-square"></i> Yes
</span> </span>
{% else %} {% else %}
<span class="text-danger"> <span class="text-danger">
<i class="far fa-times-square"></i> No <i class="far fa-times-square"></i> No
</span> </span>
{% end %} {% end %}
</td> </td>
<td id="server_list_{{user.user_id}}"> <td id="server_list_{{user.user_id}}">
<ul id="{{user.user_id}}"> <ul id="{{user.user_id}}">
{% for item in data['auth-servers'][user.user_id] %} {% for item in data['auth-servers'][user.user_id] %}
<li>{{item}}</li> <li>{{item}}</li>
{% end %} {% end %}
</ul> </ul>
</td> </td>
<td id="role_list_{{user.user_id}}"> <td id="role_list_{{user.user_id}}">
<ul> <ul>
{% for item in data['user-roles'][user.user_id] %} {% for item in data['user-roles'][user.user_id] %}
<li data-toggle="tooltip" title="{{ item }}">{{item}}</li> <li data-toggle="tooltip" title="{{ item }}">{{item}}</li>
{% end %} {% end %}
</ul> </ul>
</td> </td>
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td> <td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr> </tr>
{% end %} {% end %}
</tbody> </tbody>
</table> </table>
</div> </div>
@ -93,9 +98,15 @@
<div class="col-md-12 col-lg-12 grid-margin stretch-card"> <div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles', data['lang']) }}</h4> <h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles',
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span> data['lang']) }}</h4>
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{ translate('panelConfig', 'newRole', data['lang']) }}</a></div> {% if data['user_data']['hints'] %}
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('panelConfig', 'newRole', data['lang']) }}</a></div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
@ -110,7 +121,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for role in data['roles'] %} {% for role in data['roles'] %}
<tr> <tr>
<td>{{ role.role_name }}</td> <td>{{ role.role_name }}</td>
<td id="role_list_{{role.role_id}}"> <td id="role_list_{{role.role_id}}">
@ -120,19 +131,20 @@
{% end %} {% end %}
</ul> </ul>
</td> </td>
<td><ul> <td>
{% for user in data['users'] %} <ul>
{% for user in data['users'] %}
{% for ruser in data['user-roles'][user.user_id] %} {% for ruser in data['user-roles'][user.user_id] %}
{% if ruser == role.role_name %} {% if ruser == role.role_name %}
<li>{{ user.username }}</li> <li>{{ user.username }}</li>
{% end %} {% end %}
{% end %} {% end %}
{% end %} {% end %}
</ul> </ul>
</td> </td>
<td><a href="/panel/edit_role?id={{role.role_id}}"><i class="fas fa-pencil-alt"></i></a></td> <td><a href="/panel/edit_role?id={{role.role_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr> </tr>
{% end %} {% end %}
</tbody> </tbody>
</table> </table>
@ -146,10 +158,12 @@
<div class="col-md-12 col-lg-12 grid-margin stretch-card"> <div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card"> <div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls', data['lang']) }}</h4> <h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls',
data['lang']) }}</h4>
</div> </div>
<div class="card-body"> <div class="card-body">
<button type="button" class="btn btn-outline-danger clear-comm">{{ translate('panelConfig', 'clearComms', data['lang']) }}</button> <button type="button" class="btn btn-outline-danger clear-comm">{{ translate('panelConfig',
'clearComms', data['lang']) }}</button>
</div> </div>
</div> </div>
@ -165,9 +179,10 @@
</div> </div>
<style> <style>
.popover-body{ .popover-body {
color: white !important;; color: white !important;
} ;
}
</style> </style>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
@ -175,46 +190,46 @@
{% block js %} {% block js %}
<script> <script>
$(document).ready(function(){ $(document).ready(function () {
$('[data-toggle="popover"]').popover(); $('[data-toggle="popover"]').popover();
if($(window).width() < 1000){ if ($(window).width() < 1000) {
$('.too_small').popover("show"); $('.too_small').popover("show");
$('.too_small2').popover("show"); $('.too_small2').popover("show");
} }
}); });
$(window).ready(function(){ $(window).ready(function () {
$('body').click(function(){ $('body').click(function () {
$('.too_small').popover("hide"); $('.too_small').popover("hide");
$('.too_small2').popover("hide"); $('.too_small2').popover("hide");
}); });
}); });
$(window).resize(function() { $(window).resize(function () {
// This will execute whenever the window is resized // This will execute whenever the window is resized
if($(window).width() < 1000){ if ($(window).width() < 1000) {
$('.too_small').popover("show"); $('.too_small').popover("show");
} }
else{ else {
$('.too_small').popover("hide"); $('.too_small').popover("hide");
} // New width } // New width
if($(window).width() < 1000){ if ($(window).width() < 1000) {
$('.too_small2').popover("show"); $('.too_small2').popover("show");
} }
else{ else {
$('.too_small2').popover("hide"); $('.too_small2').popover("hide");
} // New width } // New width
}); });
</script> </script>
<script> <script>
$( document ).ready(function() { $(document).ready(function () {
console.log('ready for JS!') console.log('ready for JS!')
}); });
$( ".show_button" ).click(function() { $(".show_button").click(function () {
console.log("showing key"); console.log("showing key");
api_key = $(this).attr("data-id"); api_key = $(this).attr("data-id");
bootbox.alert({ bootbox.alert({
backdrop: true, backdrop: true,
title: '', title: '',
@ -225,13 +240,13 @@ $( document ).ready(function() {
$('.clear-comm').click(function () { $('.clear-comm').click(function () {
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: {'X-XSRFToken': token}, headers: { 'X-XSRFToken': token },
url: '/ajax/clear_comm', url: '/ajax/clear_comm',
success: function (data) { success: function (data) {
}, },
}); });
}) })
</script> </script>
{% end %} {% end %}

View File

@ -37,180 +37,238 @@
<div class="col-sm-12 grid-margin"> <div class="col-sm-12 grid-margin">
<div class="card"> <div class="card">
<div class="card-body pt-0"> <div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist"> <ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=config" role="tab" aria-selected="true"> <a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>{{ translate('rolesConfig', 'config', data['lang']) }}</a> <i class="fas fa-cogs"></i>{{ translate('rolesConfig', 'config', data['lang']) }}</a>
</li> </li>
<!-- <li class="nav-item"> <!-- <li class="nav-item">
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=other" role="tab" aria-selected="false"> <a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a> <i class="fas fa-folder-tree"></i>Other</a>
</li> --> </li> -->
</ul> </ul>
<div class="row"> <div class="">
<div class="col-md-6 col-sm-12"> <div class="">
{% if data['new_role'] %} <form class="forms-sample" method="post" action="{{ '/panel/add_role' if data['new_role'] else '/panel/edit_role' }}">
<form class="forms-sample" method="post" action="/panel/add_role"> {% raw xsrf_form_html() %}
{% else %} <input type="hidden" name="id" value="{{ data['role']['role_id'] }}">
<form class="forms-sample" method="post" action="/panel/edit_role"> <input type="hidden" name="subpage" value="config">
{% end %}
{% 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">
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('rolesConfig', 'roleTitle', data['lang']) }}</h4> <h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('rolesConfig', 'roleTitle', data['lang']) }}</h4>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="role_name">{{ translate('rolesConfig', 'roleName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('rolesConfig', 'roleDesc', data['lang']) }}</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" > <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> {{ 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">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>{{ translate('rolesConfig', 'serverName', data['lang']) }}</th>
<th>{{ translate('rolesConfig', 'serverAccess', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for server in data['servers_all'] %}
<tr>
<td>{{ server['server_name'] }}</td>
<td>
{% if server['server_id'] in data['role']['servers'] %}
<input type="checkbox" class="" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="1">
{% else %}
<input type="checkbox" class="" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" value="1">
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</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> {{ 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="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>{{ translate('rolesConfig', 'permName', data['lang']) }}</th>
<th>{{ translate('rolesConfig', 'permAccess', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for permission in data['permissions_all'] %}
<tr>
<td>{{ permission.name }}</td>
<td>
{% if permission in data['permissions_list'] %}
<input type="checkbox" class="" id="permission_{{ permission.name }}" name="permission_{{ permission.name }}" checked="" value="1">
{% else %}
<input type="checkbox" class="" id="permission_{{ permission.name }}" name="permission_{{ permission.name }}" value="1">
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('panelConfig', 'save', data['lang']) }}</button>
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light"><i class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}</button>
</form>
</div>
<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> {{ translate('rolesConfig', 'roleUsers', data['lang']) }}</h4>
</div> </div>
<div class="card-body"> </div>
<div class="table-responsive"> </div>
<table class="table table-hover">
<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('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">
<div class="table-responsive rotate-table-parent">
<table class="table table-hover rotate-table">
<thead> <thead>
<style>
.rotate-table-parent {
padding-top: 2.5rem;
padding-right: 4rem;
}
/* https://css-tricks.com/rotated-table-column-headers-now-with-fewer-magic-numbers/ */
table.rotate-table {
--table-border-width: 1px;
border-collapse: collapse;
}
th.rotate-column-header {
/* Something you can count on */
height: 140px;
white-space: nowrap;
}
th.rotate-column-header > div {
transform:
/* Magic Numbers */
translate(0px, 51px)
/* 315 is 360 - 45 */
rotate(315deg);
width: 30px;
}
th.rotate-column-header > div > span {
border-bottom: 1px solid #ccc;
padding: 5px 10px;
}
th.rotate {
white-space: nowrap;
position: relative;
}
th.rotate > div {
/* place div at bottom left of the th parent */
position: absolute;
bottom: 0;
left: 0;
/* Make sure short labels still meet the corner of the parent otherwise you'll get a gap */
text-align: left;
/* Move the top left corner of the span's bottom-border to line up with the top left corner of the td's border-right border so that the border corners are matched
* Rotate 315 (-45) degrees about matched border corners */
transform:
translate(calc(100% - var(--table-border-width) / 2), var(--table-border-width))
rotate(-45deg);
transform-origin: 0% calc(100% - var(--table-border-width));
transition: transform 500ms;
width: 100%;
}
th.rotate > div > span {
/* make sure the bottom of the span is matched up with the bottom of the parent div */
position: absolute;
bottom: 0;
left: 0;
border-bottom: var(--table-border-width) solid #383e5d;
transition: border-bottom-color 500ms;
padding-bottom: 5px;
user-select: none;
}
table.rotate-table > tbody td {
border-right: var(--table-border-width) solid #383e5d;
/* make sure this is at least as wide as sqrt(2) * height of the tallest letter in your font or the headers will overlap each other*/
min-width: 30px;
padding-top: 2px;
padding-left: 5px;
text-align: right;
}
@media screen and (min-width: 1650px) {
th.rotate > div {
transform: translate(15px, 0px) rotate(0deg);
}
th.rotate > div > span {
border-bottom-color: transparent;
}
}
</style>
<tr class="rounded"> <tr class="rounded">
<th>{{ translate('rolesConfig', 'roleUserName', data['lang']) }}</th> <th>{{ translate('rolesConfig', 'serverName', data['lang']) }}</th>
<th></th> <th class="rotate"><div><span>{{ translate('rolesConfig', 'serverAccess', data['lang']) }}</span></div></th>
{% for permission in data['permissions_all'] %}
<th class="rotate"><div><span>{{ permission.name }}</span></div></th>
{% end %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in data['users'] %} {% for server in data['servers_all'] %}
{% for ruser in data['user-roles'][user.user_id] %} <tr>
{% if ruser == data['role']['role_name'] %} <td>{{ server['server_name'] }}</td>
<tr> <td>
<td>{{ user.username }}</td> <input type="checkbox" class="" onclick="enable_disable(event)" data-id="{{server['server_id']}}"
<td> id="server_{{ server['server_id'] }}_access"
<a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-user-edit"></i></a> name="server_{{ server['server_id'] }}_access"
</td> {{ 'checked' if server['server_id'] in data['role']['servers'] else '' }}
</tr> autocomplete="off" value="1">
</td>
{% for permission in data['permissions_all'] %}
{% if server['server_id'] in data['role']['servers'] %}
<td>
<input type="checkbox" class="{{server['server_id']}}_perms"
id="permission_{{ server['server_id'] }}_{{ permission.name }}"
name="permission_{{ server['server_id'] }}_{{ permission.name }}"
{{ 'checked' if permission in data['permissions_dict'].get(server['server_id'], []) else '' }}
autocomplete="off" value="1">
</td>
{% else %}
<td>
<input type="checkbox" class="{{server['server_id']}}_perms"
id="permission_{{ server['server_id'] }}_{{ permission.name }}"
name="permission_{{ server['server_id'] }}_{{ permission.name }}"
autocomplete="off" value="1" disabled>
</td>
{% end %} {% end %}
{% end %} {% end %}
</tr>
{% end %} {% end %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
<br> </div>
</div>
<div class="col-md-3 col-sm-12"> <div class="card">
<div class="card"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<div class="card-body"> <h4 class="card-title"><i class="fas fa-settings"></i> {{ translate('panelConfig', 'save', data['lang']) }}</h4>
<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">
{{ translate('rolesConfig', 'created', data['lang']) }} {{ str(data['role']['created']) }}
<br />
{{ 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>{{ 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>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a>
{% end %}
</div>
</div>
</div> </div>
<div class="card-body">
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('panelConfig', 'save', data['lang']) }}</button>
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light"><i class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}</button>
</div>
</div>
</form>
<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> {{ translate('rolesConfig', 'roleUsers', data['lang']) }}</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>{{ translate('rolesConfig', 'roleUserName', data['lang']) }}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in data['users'] %}
{% for ruser in data['user-roles'][user.user_id] %}
{% if ruser == data['role']['role_name'] %}
<tr>
<td>{{ user.username }}</td>
<td>
<a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-user-edit"></i></a>
</td>
</tr>
{% end %}
{% end %}
{% end %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<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">
{{ translate('rolesConfig', 'created', data['lang']) }} {{ str(data['role']['created']) }}
<br />
{{ 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>{{ 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>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a>
{% end %}
</div>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
@ -219,7 +277,19 @@
{% block js %} {% block js %}
<script> <script>
function enable_disable(event) {
let server_id = event.target.getAttribute('data-id');
console.log(server_id);
if (document.getElementById("server_" + server_id + "_access").checked) {
$('.'+server_id+'_perms').attr('disabled', false);
$('.'+server_id+'_perms').attr('enabled', true);
}else{
$('.'+server_id+'_perms').prop('checked', false);
$('.'+server_id+'_perms').attr('disabled', true);
$('.'+server_id+'_perms').attr('enabled', false);
}
}
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) { function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");

View File

@ -9,22 +9,22 @@
<div class="content-wrapper"> <div class="content-wrapper">
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
{% if data['new_user'] %} {% if data['new_user'] %}
<h4 class="page-title"> <h4 class="page-title">
{{ translate('userConfig', 'pageTitleNew', data['lang']) }} {{ translate('userConfig', 'pageTitleNew', data['lang']) }}
<br /> <br />
<small>UID: N/A</small> <small>UID: N/A</small>
</h4> </h4>
{% else %} {% else %}
<h4 class="page-title"> <h4 class="page-title">
{{ translate('userConfig', 'pageTitle', data['lang']) }} - {{ data['user']['user_id'] }} {{ translate('userConfig', 'pageTitle', data['lang']) }}
<br /> <br />
<small>UID: {{ data['user']['user_id'] }}</small> <small>UID: {{ data['user']['user_id'] }}</small>
</h4> </h4>
{% end %} {% end %}
</div> </div>
</div> </div>
@ -37,198 +37,251 @@
<div class="col-sm-12 grid-margin"> <div class="col-sm-12 grid-margin">
<div class="card"> <div class="card">
<div class="card-body pt-0"> <div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist"> <ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="/panel/{{ 'add_user' if data['new_user'] else 'edit_user' }}?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true"> <a class="nav-link active"
<i class="fas fa-cogs"></i> {{ translate('userConfig', 'config', data['lang']) }} - {{ data['user']['user_id'] }}</a> href="/panel/{{ 'add_user' if data['new_user'] else 'edit_user' }}?id={{ data['user']['user_id'] }}&subpage=config"
</li> role="tab" aria-selected="true">
{% if not data['new_user'] %} <i class="fas fa-cogs"></i> {{ translate('userConfig', 'config', data['lang']) }}</a>
<li class="nav-item"> </li>
<a class="nav-link" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}" role="tab" aria-selected="false"> {% if not data['new_user'] %}
<i class="fas fa-key"></i>{{ translate('userConfig', 'apiKey', data['lang']) }} - {{ data['user']['user_id'] }}</a> <li class="nav-item">
</li> <a class="nav-link" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}" role="tab"
{% end %} aria-selected="false">
</ul> <i class="fas fa-key"></i>{{ translate('userConfig', 'apiKey', data['lang']) }}</a>
</li>
{% end %}
</ul>
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
{% if data['new_user'] %} {% if data['new_user'] %}
<form id="user_form" class="forms-sample" method="post" action="/panel/add_user"> <form id="user_form" class="forms-sample" method="post" action="/panel/add_user">
{% else %} {% else %}
<form id="user_form" class="forms-sample" method="post" action="/panel/edit_user"> <form id="user_form" class="forms-sample" method="post" action="/panel/edit_user">
{% end %} {% end %}
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}"> <input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
<input type="hidden" name="subpage" value="config"> <input type="hidden" name="subpage" value="config">
<div class="card"> <div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user"></i> {{ translate('userConfig', 'userSettings', data['lang']) }} - {{ data['user']['user_id'] }}</h4> <h4 class="card-title"><i class="fas fa-user"></i> {{ translate('userConfig', 'userSettings',
</div> data['lang']) }}</h4>
<div class="card-body"> </div>
<div class="form-group"> <div class="card-body">
<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> <div class="form-group">
<input type="text" class="form-control" name="username" id="username" value="{{ data['user']['username'] }}" placeholder="User Name" > <label class="form-label" for="username">{{ translate('userConfig', 'userName', data['lang'])
</div> }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'userNameDesc', data['lang'])
<div class="form-group"> }}</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="text" class="form-control" name="username" id="username"
<input type="password" class="form-control" name="password0" id="password0" value="" placeholder="Password" > value="{{ data['user']['username'] }}" placeholder="User Name">
</div>
<div class="form-group">
<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="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>
</div>
</div> </div>
<div class="form-group">
<div class="card"> <label class="form-label" for="password0">{{ translate('userConfig', 'password', data['lang'])
<div class="card-header header-sm d-flex justify-content-between align-items-center"> }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang']) }}
<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> </small> </label>
</div> <input type="password" class="form-control" name="password0" id="password0" value=""
<div class="card-body"> placeholder="Password">
<div class="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>{{ translate('userConfig', 'roleName', data['lang']) }}</th>
<th>{{ translate('userConfig', 'member', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for role in data['roles_all'] %}
<tr>
<td>{{ role.role_name }}</td>
<td>
{% if role.role_id in data['user']['roles'] %}
<input type="checkbox" class="form-check-input" id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership" checked="" value="1">
{% else %}
<input type="checkbox" class="form-check-input" id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership" value="1">
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div> </div>
<div class="form-group">
<!-- Put Permissions Crafty part here --> <label class="form-label" for="password1">{{ translate('userConfig', 'repeat', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang'])
<div class="card"> }}</small> </label>
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <input type="password" class="form-control" name="password1" id="password1" value=""
<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> placeholder="Repeat Password">
</div>
<div class="card-body">
<div class="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>{{ translate('userConfig', 'permName', data['lang']) }}</th>
<th>{{ translate('userConfig', 'auth', data['lang']) }}</th>
<th>{{ translate('userConfig', 'uses', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for permission in data['permissions_all'] %}
<tr>
<td>{{ permission.name }}</td>
<td>
{% if permission in data['permissions_list'] %}
<input type="checkbox" class="form-check-input" id="permission_{{ permission.name }}" name="permission_{{ permission.name }}" checked="" value="1">
{% else %}
<input type="checkbox" class="form-check-input" id="permission_{{ permission.name }}" name="permission_{{ permission.name }}" value="1">
{% end %}
</td>
<td><input type="text" class="form-control" name="quantity_{{ permission.name }}" id="quantity_{{ permission.name }}" value="{{ data['quantity_server'][permission.name] }}"></td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div> </div>
<div class="form-group">
<div class="form-check-flat"> <label class="form-label" for="email">{{ translate('userConfig', 'gravEmail', data['lang'])
<label for="enabled" class="form-check-label ml-4 mb-4"> }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'gravDesc', data['lang'])
{% if data['user']['enabled'] %} }}</small> </label>
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">{{ translate('userConfig', 'enabled', data['lang']) }} <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 %} {% else %}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" value="1">{{ translate('userConfig', 'enabled', data['lang']) }} <option value="{{lang}}" disabled>{{lang}}</option>
{% end %} {% end %}
</label>
<label for="superuser" class="form-check-label ml-4 mb-4">
{% if data['user']['superuser'] %}
<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" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" {{ data['super-disabled'] }} value="1" >{{ translate('userConfig', 'super', data['lang']) }}
{% end %} {% end %}
</label> </select>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('panelConfig', 'save', data['lang']) }}</button>
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light"><i class="fas fa-undo-alt"></i> {{ translate('panelConfig', '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"><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">
{{ translate('userConfig', 'created', data['lang']) }} {{ str(data['user']['created']) }}
<br />
{{ translate('userConfig', 'lastLogin', data['lang']) }} {{ str(data['user']['last_login']) }}
<br />
{{ translate('userConfig', 'lastUpdate', data['lang']) }} {{ str(data['user']['last_update']) }}
<br />
{{ translate('userConfig', 'lastIP', data['lang']) }} {{ data['user']['last_ip'] }}
<br />
</p>
</blockquote>
</div> </div>
</div> </div>
<div class="text-center"> </div>
{% if data['new_user'] %}
<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> {{ translate('userConfig', 'deleteUserB', data['lang']) }}</a><br />
<small>{{ translate('userConfig', 'delSuper', data['lang']) }}</small>
{% else %}
<button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> {{ translate('userConfig', 'deleteUserB', data['lang']) }}</a>
{% end %}
<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('userConfig', 'userRoles',
data['lang']) }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'userRolesDesc',
data['lang']) }}</small></h4>
</div> </div>
</div> <div class="card-body">
<div class="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>{{ translate('userConfig', 'roleName', data['lang']) }}</th>
<th>{{ translate('userConfig', 'member', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for role in data['roles_all'] %}
<tr>
<td>{{ role.role_name }}</td>
<td>
{% if role.role_id in data['user']['roles'] %}
<input type="checkbox" class="form-check-input"
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
checked="" value="1">
{% else %}
<input type="checkbox" class="form-check-input"
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
value="1">
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Put Permissions Crafty part here -->
{% if data['superuser'] %}
<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> {{ 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">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>{{ translate('userConfig', 'permName', data['lang']) }}</th>
<th>{{ translate('userConfig', 'auth', data['lang']) }}</th>
<th>{{ translate('userConfig', 'uses', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for permission in data['permissions_all'] %}
<tr>
<td>{{ permission.name }}</td>
<td>
{% if permission in data['permissions_list'] %}
<input type="checkbox" class="form-check-input" id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" checked="" value="1">
{% else %}
<input type="checkbox" class="form-check-input" id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
{% end %}
</td>
<td><input type="text" class="form-control" name="quantity_{{ permission.name }}"
id="quantity_{{ permission.name }}"
value="{{ data['quantity_server'][permission.name] }}"></td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% end %}
<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">{{ translate('userConfig', 'enabled', data['lang']) }}
{% else %}
<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" 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" onclick="superConfirm()" class="form-check-input" id="superuser"
name="superuser" {{ data['super-disabled'] }} value="1">{{ translate('userConfig', 'super',
data['lang']) }}
{% end %}
</label>
<label for="hints" class="form-check-label ml-4 mb-4">
{% if data['user']['hints'] %}
<input type="checkbox" class="form-check-input" id="hints" name="hints" checked=""
value="1">Enable Hints?
{% else %}
<input type="checkbox" class="form-check-input" id="hints" name="hints" value="1"> Enable
Hints?
{% end %}
</label>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{
translate('panelConfig', 'save', data['lang']) }}</button>
<button type="reset" onclick="location.href='/panel/panel_config'" class="btn btn-light"><i
class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}</button>
</form>
</div> </div>
<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> {{ translate('userConfig', 'configArea',
data['lang']) }}</h4>
<p class="card-description"> {{ translate('userConfig', 'configAreaDesc', data['lang']) }}</p>
<blockquote class="blockquote">
<p class="mb-0">
{{ translate('userConfig', 'created', data['lang']) }} {{ str(data['user']['created']) }}
<br />
{{ translate('userConfig', 'lastLogin', data['lang']) }} {{ str(data['user']['last_login']) }}
<br />
{{ translate('userConfig', 'lastUpdate', data['lang']) }} {{ str(data['user']['last_update']) }}
<br />
{{ translate('userConfig', 'lastIP', data['lang']) }} {{ data['user']['last_ip'] }}
<br />
</p>
</blockquote>
</div>
</div>
<div class="text-center">
{% if data['new_user'] %}
<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> {{ translate('userConfig',
'deleteUserB', data['lang']) }}</a><br />
<small>{{ translate('userConfig', 'delSuper', data['lang']) }}</small>
{% else %}
<button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> {{
translate('userConfig', 'deleteUserB', data['lang']) }}</a>
{% end %}
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -245,70 +298,70 @@
<script> <script>
const userId = new URLSearchParams(document.location.search).get('id') const userId = new URLSearchParams(document.location.search).get('id')
$( ".delete-user" ).click(function() { $(".delete-user").click(function () {
var file_to_del = $(this).data("file"); var file_to_del = $(this).data("file");
console.log("User to delete is "+userId); console.log("User to delete is " + userId);
bootbox.confirm({ bootbox.confirm({
title: "{% raw translate('userConfig', 'deleteUser', data['lang']) %} "+userId, title: "{% raw translate('userConfig', 'deleteUser', data['lang']) %} " + userId,
message: "{{ translate('userConfig', 'confirmDelete', data['lang']) }}", message: "{{ translate('userConfig', 'confirmDelete', data['lang']) }}",
buttons: { buttons: {
cancel: { cancel: {
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}' label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
}, },
confirm: { confirm: {
className: 'btn-outline-danger', className: 'btn-outline-danger',
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}' 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;
} }
},
callback: function (result) {
console.log(result);
if (result === true) {
location.href = "/panel/remove_user?id=" + userId;
} }
}
}); });
}); });
function superConfirm() { function superConfirm() {
if (document.getElementById('superuser').checked){ if (document.getElementById('superuser').checked) {
bootbox.confirm({ bootbox.confirm({
title: "{{ translate('panelConfig', 'superConfirmTitle', data['lang']) }}", title: "{{ translate('panelConfig', 'superConfirmTitle', data['lang']) }}",
message: "{{ translate('panelConfig', 'superConfirm', data['lang']) }}", message: "{{ translate('panelConfig', 'superConfirm', data['lang']) }}",
buttons: { buttons: {
cancel: { cancel: {
label: '<i class="fa fa-times"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}' label: '<i class="fa fa-times"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}'
}, },
confirm: { confirm: {
className: 'btn-outline-warning', className: 'btn-outline-warning',
label: '<i class="fa fa-check"></i> {{ translate('serverBackups', 'confirm', data['lang']) }}' label: '<i class="fa fa-check"></i> {{ translate('serverBackups', 'confirm', data['lang']) }}'
} }
}, },
callback: function (result) { callback: function (result) {
if (result == true){ if (result === true) {
return; return;
}else{ } else {
document.getElementById('superuser').checked = false; document.getElementById('superuser').checked = false;
} }
} }
}); });
}else{ } else {
return return
} }
} }
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) { function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined; return r ? r[1] : undefined;
} }
$( document ).ready(function() { $(document).ready(function () {
console.log( "ready!" ); console.log("ready!");
}); });
</script> </script>

View File

@ -5,39 +5,60 @@
<div class="row"> <div class="row">
<div class="col-sm-4 mr-2"> <div class="col-sm-4 mr-2">
{% if data['server_stats']['running'] %} {% 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', 'serverStatus', data['lang']) }}:</b> <span id="status"
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }}</span><br /> class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }}</span> <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'] %} {% 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', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-danger">
<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 /> <i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang'])
<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> }}</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 %} {% else %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span><br /> <b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status"
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started" class="text-warning">{{ translate('serverStats', 'offline', data['lang']) }}</span><br /> 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> <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 %} {% end %}
<br> <br>
<b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{ data['serverTZ'] }}</span> <b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{
data['serverTZ'] }}</span>
</div> </div>
<div class="col-sm-3 mr-2"> <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', 'cpuUsage', data['lang']) }}:</b> <span id="cpu">{{
<b>{{ translate('serverStats', 'memUsage', data['lang']) }}:</b> <span id="mem" >{{ data['server_stats']['mem'] }}</span> <br /> 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'] %} {% 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 /> <b>{{ translate('serverStats', 'players', data['lang']) }}:</b> <span id="players">{{
data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}</span><br />
{% else %} {% else %}
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> <span id="players" >0/0</span><br /> <b>{{ translate('serverStats', 'players', data['lang']) }}:</b> <span id="players">0/0</span><br />
{% end %} {% end %}
</div> </div>
<div class="col-sm-3 mr-2"> <div class="col-sm-3 mr-2">
{% if data['server_stats']['version'] != 'False' %} {% 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', 'version', data['lang']) }}:</b> <span id="version">{{
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ data['server_stats']['desc'] }}</span> <br /> data['server_stats']['version'] }}</span><br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd"
style="max-width: 10px; max-height: 10px" class="input_motd">{{ data['server_stats']['desc'] }}</span>
<br />
{% else %} {% else %}
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> <span id="version">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br /> <b>{{ translate('serverStats', 'version', data['lang']) }}:</b> <span id="version">{{
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br /> translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span
style="max-width: 10px; max-height: 10px" id="input_motd" class="input_motd">{{ translate('serverStats',
'unableToConnect', data['lang']) }}</span> <br />
{% end %} {% end %}
<b>Server Type: <span class="text-info">{{data['server_stats']['server_type']}}</span></b> <b>Server Type: <span class="text-info">{{data['server_stats']['server_type']}}</span></b>
@ -64,6 +85,15 @@
seconds: duration._data.seconds seconds: duration._data.seconds
} }
if (Math.round(duration._data.days)) {
obj = {
days: Math.round(duration._data.days),
hours: Math.round(duration._data.hours -= duration._data.days * 24),
minutes: duration._data.minutes,
seconds: duration._data.seconds
}
}
output = Object.entries(obj) output = Object.entries(obj)
.map(([type, num]) => { .map(([type, num]) => {
// make them strings // make them strings
@ -135,9 +165,9 @@
server_input_motd = document.getElementById('input_motd'); server_input_motd = document.getElementById('input_motd');
/* TODO Update each element */ /* TODO Update each element */
if (server.running){ if (server.running) {
server_status.setAttribute("class", "text-success"); server_status.setAttribute("class", "text-success");
server_status.innerHTML = `{{ translate('serverStats', 'online', data['lang']) }}`; server_status.innerHTML = `{{ translate('serverStats', 'online', data['lang']) }}`;
startedUTC = server.started; startedUTC = server.started;
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss'); startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
@ -156,9 +186,8 @@
uptimeLoop = setInterval(calculateUptime, 1000); uptimeLoop = setInterval(calculateUptime, 1000);
} }
} }
else else {
{ if (server.crashed) {
if (server.crashed){
server_status.setAttribute("class", "text-danger"); server_status.setAttribute("class", "text-danger");
server_status.innerHTML = `<i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}`; server_status.innerHTML = `<i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}`;
server_started.setAttribute("class", "text-danger"); server_started.setAttribute("class", "text-danger");
@ -167,37 +196,33 @@
uptimeLoop = null; uptimeLoop = null;
server_uptime.setAttribute("class", "text-danger"); server_uptime.setAttribute("class", "text-danger");
server_uptime.innerHTML = `<i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}`; server_uptime.innerHTML = `<i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard', 'crashed', data['lang']) }}`;
}else{ } else {
server_status.setAttribute("class", "text-warning"); server_status.setAttribute("class", "text-warning");
server_status.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`; server_status.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
server_started.setAttribute("class", "text-warning"); server_started.setAttribute("class", "text-warning");
server_started.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`; server_started.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
clearInterval(uptimeLoop); clearInterval(uptimeLoop);
uptimeLoop = null; uptimeLoop = null;
server_uptime.setAttribute("class", "text-warning"); server_uptime.setAttribute("class", "text-warning");
server_uptime.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`; server_uptime.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
} }
} }
server_cpu.innerHTML = server.cpu + ` %`; server_cpu.innerHTML = server.cpu + ` %`;
server_mem.innerHTML = server.mem; server_mem.innerHTML = server.mem;
if (server.int_ping_results) if (server.int_ping_results) {
{
server_players.innerHTML = server.online + `/` + server.max; server_players.innerHTML = server.online + `/` + server.max;
} }
else else {
{
server_players.innerHTML = `0/0`; server_players.innerHTML = `0/0`;
} }
if (server.version) if (server.version) {
{
server_version.innerHTML = server.version; server_version.innerHTML = server.version;
server_input_motd.innerHTML = server.desc; server_input_motd.innerHTML = server.desc;
} }
else else {
{
server_version.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`; server_version.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`;
server_input_motd.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`; server_input_motd.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`;
} }

View File

@ -0,0 +1,31 @@
<div class="col-sm-12 mt-4 mb-4">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false">
Server Controls
</button>
<div class="dropdown-menu col-md-12" aria-labelledby="dropdownMenuButton">
{% if data['permissions']['Terminal'] in data['user_permissions'] %}
<a class="dropdown-item {% if data['active_link'] == 'term' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="false"><i class="fas fa-file-signature"></i> {{ translate('serverDetails', 'terminal', data['lang']) }}</a>
{% end %}
<!--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'%}
<a class="dropdown-item {% if data['active_link'] == 'logs' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="false"><i class="fas fa-file-signature"></i> {{ translate('serverDetails', 'logs', data['lang']) }}</a>
{% end %}
{% if data['permissions']['Schedule'] in data['user_permissions'] %}
<a class="dropdown-item {% if data['active_link'] == 'schedules' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=schedules" role="tab" aria-selected="false"><i class="fas fa-clock"></i> {{ translate('serverDetails', 'schedule', data['lang']) }}</a>
{% end %}
{% if data['permissions']['Backup'] in data['user_permissions'] %}
<a class="dropdown-item {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false"><i class="fas fa-save"></i> {{ translate('serverDetails', 'backup', data['lang']) }}</a>
{% end %}
{% if data['permissions']['Files'] in data['user_permissions'] %}
<a class="dropdown-item {% if data['active_link'] == 'files' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false"><i class="fas fa-folder-tree"></i> {{ translate('serverDetails', 'files', data['lang']) }}</a>
{% end %}
{% if data['permissions']['Config'] in data['user_permissions'] %}
<a class="dropdown-item {% if data['active_link'] == 'config' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="true"><i class="fas fa-cogs"></i> {{ translate('serverDetails', 'config', data['lang']) }}</a>
{% end %}
{% if data['permissions']['Players'] in data['user_permissions'] and data['server_data']['type'] != 'minecraft-bedrock' %}
<a class="dropdown-item {% 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>
{% end %}
</div>
</div>
</div>

View File

@ -9,14 +9,14 @@
<div class="content-wrapper"> <div class="content-wrapper">
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title"> <h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }} {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>
@ -24,71 +24,78 @@
</div> </div>
<!-- Page Title Header Ends--> <!-- Page Title Header Ends-->
{% include "parts/details_stats.html %} {% include "parts/details_stats.html %}
<div class="row"> <div class="row">
<div class="col-sm-12 grid-margin"> <div class="col-sm-12 grid-margin">
<div class="card"> <div class="card">
<div class="card-body pt-0"> <div class="card-body pt-0">
<span class="d-none d-sm-block">
{% include "parts/server_controls_list.html %} {% include "parts/server_controls_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_server_controls_list.html %}
</span>
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<style> <style>
.playerItem { .playerItem {
background: #1c1e2f; background: #1c1e2f;
padding: 1rem; padding: 1rem;
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin: 1rem 0px 1rem 0px; margin: 1rem 0px 1rem 0px;
} }
.playerItem h3 {
vertical-align: middle;
padding: 0px;
margin: 0px;
margin-right: 1.5rem;
}
.playerItem button {
vertical-align: middle;
margin: 0.25rem;
}
.playerUnban { .playerItem h3 {
margin-bottom: 1rem; vertical-align: middle;
} padding: 0px;
margin: 0px;
margin-right: 1.5rem;
}
.banned span { .playerItem button {
font-size: 1.1rem; vertical-align: middle;
} margin: 0.25rem;
</style> }
<h2>{{ translate('serverPlayerManagement', 'players', data['lang']) }}:</h2>
<ul style="list-style: none;padding: 0px;margin: 0px; margin-bottom: 1rem;gap: 1rem;">
{% for player in data['get_players']() %}
<li class="playerItem">
<h3>{{ player }}</h3>
<div class="buttons">
<button onclick="send_command_to_server('ban {{ player }}')" type="button" class="btn btn-danger">Ban</button>
<button onclick="send_command_to_server('kick {{ player }}')" type="button" class="btn btn-outline-danger">Kick</button>
<button onclick="send_command_to_server('op {{ player }}')" type="button" class="btn btn-warning">OP</button>
<button onclick="send_command_to_server('deop {{ player }}')" type="button" class="btn btn-outline-warning">De-OP</button>
</div>
</li>
{% end %}
</ul>
</div>
<div class="col-md-6 col-sm-12">
<h2>{{ translate('serverPlayerManagement', 'bannedPlayers', data['lang']) }}:</h2>
<ul id="bannedPlayers" style="list-style: none;padding: 0px;margin: 0px; margin-bottom: 1rem;gap: 1rem;">
<li class="playerItem banned">
<h3>{{ translate('serverPlayerManagement', 'loadingBannedPlayers', data['lang']) }}</h3>
</li>
</ul> .playerUnban {
</div> margin-bottom: 1rem;
}
.banned span {
font-size: 1.1rem;
}
</style>
<h2>{{ translate('serverPlayerManagement', 'players', data['lang']) }}:</h2>
<ul style="list-style: none;padding: 0px;margin: 0px; margin-bottom: 1rem;gap: 1rem;">
{% for player in data['get_players']() %}
<li class="playerItem">
<h3>{{ player }}</h3>
<div class="buttons">
<button onclick="send_command_to_server('ban {{ player }}')" type="button" class="btn btn-danger">Ban</button>
<button onclick="send_command_to_server('kick {{ player }}')" type="button" class="btn btn-outline-danger">Kick</button>
<button onclick="send_command_to_server('op {{ player }}')" type="button" class="btn btn-warning">OP</button>
<button onclick="send_command_to_server('deop {{ player }}')" type="button" class="btn btn-outline-warning">De-OP</button>
</div>
</li>
{% end %}
</ul>
</div> </div>
<div class="col-md-6 col-sm-12">
<h2>{{ translate('serverPlayerManagement', 'bannedPlayers', data['lang']) }}:</h2>
<ul id="bannedPlayers" style="list-style: none;padding: 0px;margin: 0px; margin-bottom: 1rem;gap: 1rem;">
<li class="playerItem banned">
<h3>{{ translate('serverPlayerManagement', 'loadingBannedPlayers', data['lang']) }}</h3>
</li>
</ul>
</div>
</div>
</div> </div>
</div> </div>
@ -104,48 +111,48 @@
{% block js %} {% block js %}
<script> <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;
}
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security function htmlDecode(input) {
function getCookie(name) { var e = document.createElement('textarea');
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); e.innerHTML = input;
return r ? r[1] : undefined; // handle case of empty input
} return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
function htmlDecode(input){ $(document).ready(function () {
var e = document.createElement('textarea'); console.log("ready!");
e.innerHTML = input;
// handle case of empty input
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
$( document ).ready(function() { var bannedPlayers = `{{ data['banned_players'] }}`;
console.log( "ready!" );
var bannedPlayers = `{{ data['banned_players'] }}`; var bannedPlayersDecoded = htmlDecode(bannedPlayers);
var bannedPlayersDecoded = htmlDecode(bannedPlayers); $("#bannedPlayers").html(bannedPlayersDecoded)
$("#bannedPlayers").html(bannedPlayersDecoded) });
function send_command_to_server(command) {
console.log(command)
var token = getCookie("_xsrf")
console.log('sending command: ' + command)
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/send_command?id=' + serverId,
data: { command },
success: function (data) {
console.log("got response:");
console.log(data);
},
}); });
}
function send_command_to_server (command) {
console.log(command)
var token = getCookie("_xsrf")
console.log('sending command: ' + command)
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/send_command?id=1',
data: { command },
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
</script> </script>

View File

@ -9,14 +9,14 @@
<div class="content-wrapper"> <div class="content-wrapper">
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title"> <h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }} {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>
@ -24,144 +24,161 @@
</div> </div>
<!-- Page Title Header Ends--> <!-- Page Title Header Ends-->
{% include "parts/details_stats.html %} {% include "parts/details_stats.html %}
<div class="row"> <div class="row">
<div class="col-sm-12 grid-margin"> <div class="col-sm-12 grid-margin">
<div class="card"> <div class="card">
<div class="card-body pt-0"> <div class="card-body pt-0">
<span class="d-none d-sm-block">
{% include "parts/server_controls_list.html %} {% include "parts/server_controls_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_server_controls_list.html %}
</span>
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<br> <br>
<br> <br>
<form class="forms-sample" method="post" action="/panel/server_backup"> <form class="forms-sample" method="post" action="/panel/server_backup">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}"> <input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="backup"> <input type="hidden" name="subpage" value="backup">
<div class="form-group">
<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"> {% if data['backing_up'] %}
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang']) }}</small> </label> <div class="progress" style="height: 15px;">
<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 class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar" role="progressbar" style="width:{{data['backup_stats']['percent']}}%;" aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{ data['backup_stats']['percent'] }}%</div>
</div>
<div class="form-group">
<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="compress" name="compress"
value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
{% end %}
</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">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
<input type="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>
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
</form>
</div> </div>
<p>Backing up <span id="total_files">{{data['backup_stats']['total_files']}}</span> Files</p>
<div class="col-md-6 col-sm-12">
<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>
<th>{{ translate('serverBackups', 'path', data['lang']) }}</th>
<th width="20%">{{ translate('serverBackups', 'size', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for backup in data['backup_list'] %}
<tr>
<td>
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary">
<i class="fas fa-download" aria-hidden="true"></i>
{{ translate('serverBackups', 'download', data['lang']) }}
</a>
<br>
<br>
<button data-file="{{ backup['path'] }}" data-backup_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>
</tr>
{% end %}
</tbody>
</table>
</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 %} {% end %}
</ul>
<br>
{% if not data['backing_up'] %}
<div id="backup_button" class="form-group">
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow', data['lang']) }}</button>
</div>
{% end %}
<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="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="compress" name="compress"
value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
{% end %}
</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">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
<input type="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>
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
</form>
</div> </div>
<div class="col-md-6 col-sm-12">
<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>
<th>{{ translate('serverBackups', 'path', data['lang']) }}</th>
<th width="20%">{{ translate('serverBackups', 'size', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for backup in data['backup_list'] %}
<tr>
<td>
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary">
<i class="fas fa-download" aria-hidden="true"></i>
{{ translate('serverBackups', 'download', data['lang']) }}
</a>
<br>
<br>
<button data-file="{{ backup['path'] }}" data-backup_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>
</tr>
{% end %}
</tbody>
</table>
</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>
</div> </div>
</div> </div>
@ -172,41 +189,44 @@
</div> </div>
<style> <style>
/* Remove default bullets */ /* Remove default bullets */
.tree-view, .tree-view,
.tree-nested { .tree-nested {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
margin-left: 10px; margin-left: 10px;
} }
/* Style the items */ /* Style the items */
.tree-item, .tree-item,
.files-tree-title { .files-tree-title {
cursor: pointer; cursor: pointer;
user-select: none; /* Prevent text selection */ user-select: none;
} /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */ /* Create the caret/arrow with a unicode, and style it */
.tree-caret .fa-folder { .tree-caret .fa-folder {
display: inline-block; display: inline-block;
} }
.tree-caret .fa-folder-open {
display: none;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */ .tree-caret .fa-folder-open {
.tree-caret-down .fa-folder { display: none;
display: none; }
}
.tree-caret-down .fa-folder-open {
display: inline-block;
}
/* Hide the nested list */ /* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.tree-nested { .tree-caret-down .fa-folder {
display: none; display: none;
} }
.tree-caret-down .fa-folder-open {
display: inline-block;
}
/* Hide the nested list */
.tree-nested {
display: none;
}
</style> </style>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
@ -215,7 +235,7 @@
{% block js %} {% block js %}
<script> <script>
const server_id = new URLSearchParams(document.location.search).get('id') 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 //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
@ -224,60 +244,87 @@ const server_id = new URLSearchParams(document.location.search).get('id')
return r ? r[1] : undefined; return r ? r[1] : undefined;
} }
function backup_started(time='5-10') { function backup_started() {
bootbox.alert({ var token = getCookie("_xsrf")
document.getElementById('backup_button').style.visibility = 'hidden';
var dialog = bootbox.dialog({
message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}", message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}",
backdrop: true closeButton: false
});
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/backup_now?id=' + server_id,
success: function (data) {
},
}); });
} }
function del_backup(filename, id){ function del_backup(filename, id) {
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
data_to_send = { file_name :filename} data_to_send = { file_name: filename }
console.log('Sending Command to delete backup: ' + filename) console.log('Sending Command to delete backup: ' + filename)
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: {'X-XSRFToken': token}, headers: { 'X-XSRFToken': token },
url: '/ajax/del_backup?server_id='+id, url: '/ajax/del_backup?server_id=' + id,
data: { data: {
file_path: filename, file_path: filename,
id: id id: id
}, },
success: function(data) { success: function (data) {
location.reload(); location.reload();
}, },
}); });
} }
function restore_backup(filename, id){ function restore_backup(filename, id) {
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
var dialog = bootbox.dialog({ var dialog = bootbox.dialog({
message: '<i class="fa fa-spin fa-spinner"></i> {{ translate('serverBackups', 'restoring', data['lang']) }}', message: '<i class="fa fa-spin fa-spinner"></i> {{ translate('serverBackups', 'restoring', data['lang']) }}',
closeButton: false closeButton: false
}); });
console.log('Sending Command to restore backup: ' + filename) console.log('Sending Command to restore backup: ' + filename)
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: {'X-XSRFToken': token}, headers: { 'X-XSRFToken': token },
url: '/ajax/restore_backup?server_id='+id, url: '/ajax/restore_backup?server_id=' + id,
data: { data: {
zip_file: filename, zip_file: filename,
id: id id: id
}, },
success: function(data) { success: function (data) {
setTimeout(function(){ setTimeout(function () {
location.href=('/panel/dashboard'); location.href = ('/panel/dashboard');
}, 15000); }, 15000);
}, },
}); });
} }
$( document ).ready(function() { $(document).ready(function () {
console.log( "ready!" ); try {
if ($('#backup_path').val() == '') {
console.log('true')
try {
document.getElementById('backup_now_button').disabled = true;
} catch {
}
} else {
document.getElementById('backup_now_button').disabled = false;
}
} catch {
try {
document.getElementById('backup_now_button').disabled = false;
} catch {
}
}
console.log("ready!");
$("#backup_config_box").hide(); $("#backup_config_box").hide();
$("#backup_save_note").hide(); $("#backup_save_note").hide();
@ -289,8 +336,8 @@ const server_id = new URLSearchParams(document.location.search).get('id')
}); });
$('#backup_table').DataTable({ $('#backup_table').DataTable({
"order": [[ 1, "desc" ]], "order": [[1, "desc"]],
"paging":true, "paging": true,
"lengthChange": false, "lengthChange": false,
"searching": true, "searching": true,
"ordering": true, "ordering": true,
@ -299,7 +346,7 @@ const server_id = new URLSearchParams(document.location.search).get('id')
"responsive": true, "responsive": true,
}); });
$( ".del_button" ).click(function() { $(".del_button").click(function () {
var file_to_del = $(this).data("file"); var file_to_del = $(this).data("file");
var backup_path = $(this).data('backup_path'); var backup_path = $(this).data('backup_path');
@ -326,12 +373,12 @@ const server_id = new URLSearchParams(document.location.search).get('id')
}); });
}); });
$( ".restore_button" ).click(function() { $(".restore_button").click(function () {
var file_to_restore = $(this).data("file"); var file_to_restore = $(this).data("file");
bootbox.confirm({ bootbox.confirm({
title: "{{ translate('serverBackups', 'restore', data['lang']) }} "+file_to_restore, title: "{{ translate('serverBackups', 'restore', data['lang']) }} " + file_to_restore,
message: "{{ translate('serverBackups', 'confirmRestore', data['lang']) }}", message: "{{ translate('serverBackups', 'confirmRestore', data['lang']) }}",
buttons: { buttons: {
cancel: { cancel: {
@ -339,7 +386,7 @@ const server_id = new URLSearchParams(document.location.search).get('id')
}, },
confirm: { confirm: {
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "restore", data['lang']) }}', label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "restore", data['lang']) }}',
className: 'btn-outline-danger' className: 'btn-outline-danger'
} }
}, },
callback: function (result) { callback: function (result) {
@ -350,43 +397,46 @@ const server_id = new URLSearchParams(document.location.search).get('id')
} }
}); });
}); });
$("#backup_now_button").click(function () {
backup_started();
});
}); });
document.getElementById("modal-cancel").addEventListener("click", function(){ document.getElementById("modal-cancel").addEventListener("click", function () {
document.getElementById("root_files_button").classList.remove('clicked'); 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("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(){ document.getElementById("root_files_button").addEventListener("click", function () {
if($("#root_files_button").data('server_path') != ""){ if ($("#root_files_button").data('server_path') != "") {
if(document.getElementById('root_files_button').classList.contains('clicked')){ if (document.getElementById('root_files_button').classList.contains('clicked')) {
show_file_tree(); show_file_tree();
return; return;
}else{ } else {
document.getElementById('root_files_button').classList.add('clicked'); document.getElementById('root_files_button').classList.add('clicked');
document.getElementById("changed").value = 1; document.getElementById("changed").value = 1;
} }
path = $("#root_files_button").data('server_path') path = $("#root_files_button").data('server_path')
console.log($("#root_files_button").data('server_path')) console.log($("#root_files_button").data('server_path'))
var token = getCookie("_xsrf"); var token = getCookie("_xsrf");
var dialog = bootbox.dialog({ 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>', 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 closeButton: false
}); });
$.ajax({ $.ajax({
type: "POST", type: "POST",
headers: {'X-XSRFToken': token}, headers: { 'X-XSRFToken': token },
url: '/ajax/backup_select?id='+server_id+'&path='+path, url: '/ajax/backup_select?id=' + server_id + '&path=' + path,
}); });
}else{ } else {
bootbox.alert("You must input a path before selecting this button"); bootbox.alert("You must input a path before selecting this button");
} }
}); });
if (webSocket) { if (webSocket) {
webSocket.on('send_temp_path', function (data) { webSocket.on('send_temp_path', function (data) {
setTimeout(function(){ setTimeout(function () {
var x = document.querySelector('.bootbox'); var x = document.querySelector('.bootbox');
if (x) { if (x) {
x.remove() x.remove()
@ -399,7 +449,27 @@ document.getElementById("modal-cancel").addEventListener("click", function(){
getTreeView(data.path); getTreeView(data.path);
show_file_tree(); show_file_tree();
}, 5000); }, 5000);
});
}
if (webSocket) {
webSocket.on('backup_status', function (backup) {
if (backup.percent >= 100) {
document.getElementById('backup_progress_bar').innerHTML = '100%';
document.getElementById('backup_progress_bar').style.width = '100%';
setTimeout(function () {
window.location.reload(1);
}, 5000);
} else {
document.getElementById('backup_progress_bar').innerHTML = backup.percent + '%';
document.getElementById('backup_progress_bar').style.width = backup.percent + '%';
document.getElementById('total_files').innerHTML = backup.total_files;
}
});
}
if (webSocket) {
webSocket.on('backup_reload', function (backup) {
location.reload()
}); });
} }
@ -408,9 +478,9 @@ document.getElementById("modal-cancel").addEventListener("click", function(){
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: '/ajax/get_backup_tree?id='+server_id+'&path='+path, url: '/ajax/get_backup_tree?id=' + server_id + '&path=' + path,
dataType: 'text', dataType: 'text',
success: function(data){ success: function (data) {
console.log("got response:"); console.log("got response:");
console.log(data); console.log(data);
@ -418,10 +488,10 @@ document.getElementById("modal-cancel").addEventListener("click", function(){
serverDir = dataArr.shift(); // Remove & return first element (server directory) serverDir = dataArr.shift(); // Remove & return first element (server directory)
text = dataArr.join('\n'); text = dataArr.join('\n');
try{ try {
document.getElementById('main-tree-div').innerHTML += text; document.getElementById('main-tree-div').innerHTML += text;
document.getElementById('main-tree').parentElement.classList.add("clicked"); document.getElementById('main-tree').parentElement.classList.add("clicked");
}catch{ } catch {
document.getElementById('files-tree').innerHTML = text; document.getElementById('files-tree').innerHTML = text;
} }
@ -434,57 +504,57 @@ document.getElementById("modal-cancel").addEventListener("click", function(){
} }
function getToggleMain(event) { function getToggleMain(event) {
path = event.target.parentElement.getAttribute('data-path'); path = event.target.parentElement.getAttribute('data-path');
document.getElementById("files-tree").classList.toggle("d-block"); 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-down");
document.getElementById(path+"span").classList.toggle("tree-caret"); document.getElementById(path + "span").classList.toggle("tree-caret");
} }
function getDirView(event) { function getDirView(event) {
path = event.target.parentElement.getAttribute('data-path'); path = event.target.parentElement.getAttribute('data-path');
if (document.getElementById(path).classList.contains('clicked')){ if (document.getElementById(path).classList.contains('clicked')) {
var toggler = document.getElementById(path+"span"); var toggler = document.getElementById(path + "span");
if (toggler.classList.contains('files-tree-title')){ if (toggler.classList.contains('files-tree-title')) {
document.getElementById(path+"ul").classList.toggle("d-block"); document.getElementById(path + "ul").classList.toggle("d-block");
document.getElementById(path+"span").classList.toggle("tree-caret-down"); document.getElementById(path + "span").classList.toggle("tree-caret-down");
} }
return; return;
}else{ } else {
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: '/ajax/get_backup_dir?id='+server_id+'&path='+path, url: '/ajax/get_backup_dir?id=' + server_id + '&path=' + path,
dataType: 'text', dataType: 'text',
success: function(data){ success: function (data) {
console.log("got response:"); console.log("got response:");
dataArr = data.split('\n'); dataArr = data.split('\n');
serverDir = dataArr.shift(); // Remove & return first element (server directory) serverDir = dataArr.shift(); // Remove & return first element (server directory)
text = dataArr.join('\n'); text = dataArr.join('\n');
try{ try {
document.getElementById(path+"span").classList.add('tree-caret-down'); document.getElementById(path + "span").classList.add('tree-caret-down');
document.getElementById(path).innerHTML += text; document.getElementById(path).innerHTML += text;
document.getElementById(path).classList.add("clicked"); document.getElementById(path).classList.add("clicked");
}catch{ } catch {
console.log("Bad") console.log("Bad")
} }
var toggler = document.getElementById(path); var toggler = document.getElementById(path);
if (toggler.classList.contains('files-tree-title')){ if (toggler.classList.contains('files-tree-title')) {
document.getElementById(path+"span").addEventListener("click", function caretListener() { document.getElementById(path + "span").addEventListener("click", function caretListener() {
document.getElementById(path+"ul").classList.toggle("d-block"); document.getElementById(path + "ul").classList.toggle("d-block");
document.getElementById(path+"span").classList.toggle("tree-caret-down"); document.getElementById(path + "span").classList.toggle("tree-caret-down");
}); });
} }
}, },
}); });
}
} }
} function show_file_tree() {
function show_file_tree(){
$("#dir_select").modal(); $("#dir_select").modal();
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,14 +9,14 @@
<div class="content-wrapper"> <div class="content-wrapper">
<!-- Page Title Header Starts--> <!-- Page Title Header Starts-->
<div class="row page-title-header"> <div class="row page-title-header">
<div class="col-12"> <div class="col-12">
<div class="page-header"> <div class="page-header">
<h4 class="page-title"> <h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }} {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>
@ -24,208 +24,230 @@
</div> </div>
<!-- Page Title Header Ends--> <!-- Page Title Header Ends-->
{% include "parts/details_stats.html %} {% include "parts/details_stats.html %}
<div class="row"> <div class="row">
<div class="col-sm-12 grid-margin"> <div class="col-sm-12 grid-margin">
<div class="card"> <div class="card">
<div class="card-body pt-0"> <div class="card-body pt-0">
{% include "parts/server_controls_list.html %}
<div class="row"> <span class="d-none d-sm-block">
<div class="col-md-12 col-sm-12" style="overflow-x:auto;"> {% include "parts/server_controls_list.html %}
<div class="card"> </span>
<div class="card-header header-sm d-flex justify-content-between align-items-center"> <span class="d-block d-sm-none">
<h4 class="card-title"><i class="fas fa-calendar"></i> Scheduled Tasks</h4> {% include "parts/m_server_controls_list.html %}
<span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}", data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}", data-placement="bottom"></span> </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="row">
<div class="card-body"> <div class="col-md-12 col-sm-12" style="overflow-x:auto;">
<table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%" style="table-layout:fixed;"> <div class="card">
<thead> <div class="card-header header-sm d-flex justify-content-between align-items-center">
<tr class="rounded"> <h4 class="card-title"><i class="fas fa-calendar"></i> Scheduled Tasks</h4>
<th style="width: 2%; min-width: 10px;">ID</th> {% if data['user_data']['hints'] %}
<th style="width: 23%; min-width: 50px;">Action</th> <span class="too_small" title="{{ translate('serverSchedules', 'cannotSee', data['lang']) }}" ,
<th style="width: 40%; min-width: 50px;">Command</th> data-content="{{ translate('serverSchedules', 'cannotSeeOnMobile', data['lang']) }}" ,
<th style="width: 10%; min-width: 50px;">Interval</th> data-placement="bottom"></span>
<th style="width: 10%; min-width: 50px;">Start Time</th> {% end %}
<th style="width: 10%; min-width: 50px;">Enabled</th> <div><button
<th style="width: 10%; min-width: 50px;">Edit</th> onclick="location.href=`/panel/add_schedule?id={{ data['server_stats']['server_id']['server_id'] }}`"
</tr> class="btn btn-info">Create New Schedule <i class="fas fa-pencil-alt"></i></button></div>
</thead> </div>
<tbody> <div class="card-body">
{% for schedule in data['schedules'] %} <table class="table table-hover d-none d-lg-block responsive-table" id="schedule_table" width="100%"
<tr> style="table-layout:fixed;">
<td id="{{schedule.schedule_id}}" class="id"> <thead>
<p>{{schedule.schedule_id}}</p> <tr class="rounded">
</td> <th style="width: 2%; min-width: 10px;">ID</th>
<td id="{{schedule.action}}" class="action"> <th style="width: 23%; min-width: 50px;">Action</th>
<p>{{schedule.action}}</p> <th style="width: 40%; min-width: 50px;">Command</th>
</td> <th style="width: 10%; min-width: 50px;">Interval</th>
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;"> <th style="width: 10%; min-width: 50px;">Start Time</th>
<p>{{schedule.command}}</p> <th style="width: 10%; min-width: 50px;">Enabled</th>
</td> <th style="width: 10%; min-width: 50px;">Edit</th>
<td id="{{schedule.interval}}" class="action"> </tr>
{% if schedule.interval != '' %} </thead>
<p>Every</p> <tbody>
<p>{{schedule.interval}} {{schedule.interval_type}}</p> {% for schedule in data['schedules'] %}
{% elif schedule.interval_type == 'reaction' %} <tr>
<p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p> <td id="{{schedule.schedule_id}}" class="id">
{% else %} <p>{{schedule.schedule_id}}</p>
<p>Cron String:</p> </td>
<p>{{schedule.cron_string}}</p> <td id="{{schedule.action}}" class="action">
{% end %} <p>{{schedule.action}}</p>
</td> </td>
<td id="{{schedule.start_time}}" class="action"> <td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;">
<p>{{schedule.start_time}}</p> <p>{{schedule.command}}</p>
</td> </td>
<td id="{{schedule.enabled}}" class="action"> <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 %} {% if schedule.enabled %}
<span class="text-success"> <span class="text-success">
<i class="fas fa-check-square"></i> Yes <i class="fas fa-check-square"></i> Yes
</span> </span>
{% else %} {% else %}
<span class="text-danger"> <span class="text-danger">
<i class="far fa-times-square"></i> No <i class="far fa-times-square"></i> No
</span> </span>
{% end %} {% end %}
</td> </td>
<td id="{{schedule.action}}" class="action"> <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"> <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> <i class="fas fa-pencil-alt"></i>
</button> </button>
<br> <br>
<br> <br>
<button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button"> <button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i> <i class="fas fa-trash" aria-hidden="true"></i>
</button> </button>
</td> </td>
</tr> </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 %} {% end %}
</tbody> </td>
</table> </tr>
<hr /> <!-- Modal -->
<table class="table table-hover d-block d-lg-none" id="mini_schedule_table" width="100%" style="table-layout:fixed;"> <div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<thead> <div class="modal-dialog" role="document">
<tr class="rounded"> <div class="modal-content">
<th style="width: 25%; min-width: 50px;">Action</th> <div class="modal-header">
<th style="max-width: 40%; min-width: 50px;">Command</th> <h5 class="modal-title" id="exampleModalLabel">Task Details</h5>
<th style="width: 10%; min-width: 50px;">Enabled</th> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
</tr> <span aria-hidden="true">&times;</span>
</thead> </button>
<tbody> </div>
{% for schedule in data['schedules'] %} <div class="modal-body">
<tr data-toggle="modal" data-target="#task_details_{{schedule.schedule_id}}"> <ul style="list-style: none;">
<td id="{{schedule.action}}" class="action"> <li id="{{schedule.schedule_id}}" class="id" style="border-top: .1em solid gray;">
<p>{{schedule.action}}</p> <h4>ID</h4>
</td> <p>{{schedule.schedule_id}}</p>
<td id="{{schedule.command}}" class="action" style="overflow: scroll; max-width: 30px;"> </li>
<p>{{schedule.command}}</p> <li id="{{schedule.action}}" class="action" style="border-top: .1em solid gray;">
</td> <h4>Action</h4>
<td id="{{schedule.enabled}}" class="action"> <p>{{schedule.action}}</p>
{% if schedule.enabled %} </li>
<span class="text-success"> <li id="{{schedule.command}}" class="action" style="border-top: .1em solid gray;">
<i class="fas fa-check-square"></i> Yes <h4>Command</h4>
</span> <p>{{schedule.command}}</p>
{% else %} </li>
<span class="text-danger"> <li id="{{schedule.interval}}" class="action" style="border-top: .1em solid gray;">
<i class="far fa-times-square"></i> No {% if schedule.interval != '' %}
</span> <h4>Interval</h4>
{% end %} <p>Every {{schedule.interval}} {{schedule.interval_type}}</p>
</td> {% elif schedule.interval_type == 'reaction' %}
</tr> <h4>Interval</h4>
<!-- Modal --> <p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p>
<div class="modal fade" id="task_details_{{schedule.schedule_id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> {% else %}
<div class="modal-dialog" role="document"> <h4>Interval</h4>
<div class="modal-content"> <p>Cron String: {{schedule.cron_string}}</p>
<div class="modal-header"> {% end %}
<h5 class="modal-title" id="exampleModalLabel">Task Details</h5> </li>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;">
<span aria-hidden="true">&times;</span> <h4>Start Time</h4>
</button> <p>{{schedule.start_time}}</p>
</div> </li>
<div class="modal-body"> <li id="{{schedule.enabled}}" class="action" style="border-top: .1em solid gray; border-bottom: .1em solid gray">
<ul style="list-style: none;"> {% if schedule.enabled %}
<li id="{{schedule.schedule_id}}" class="id" style="border-top: .1em solid gray;"> <h4>Enabled</h4> <span class="text-success">
<h4>ID</h4><p>{{schedule.schedule_id}}</p> <i class="fas fa-check-square"></i> Yes
</li> </span>
<li id="{{schedule.action}}" class="action" style="border-top: .1em solid gray;"> {% else %}
<h4>Action</h4><p>{{schedule.action}}</p> <h4>Enabled</h4> <span class="text-danger">
</li> <i class="far fa-times-square"></i> No
<li id="{{schedule.command}}" class="action" style="border-top: .1em solid gray;"> </span>
<h4>Command</h4><p>{{schedule.command}}</p> {% end %}
</li> </li>
<li id="{{schedule.interval}}" class="action" style="border-top: .1em solid gray;"> </ul>
{% if schedule.interval != '' %} </div>
<h4>Interval</h4> <p>Every {{schedule.interval}} {{schedule.interval_type}}</p> <div class="modal-footer">
{% elif schedule.interval_type == 'reaction' %} <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">
<h4>Interval</h4> <p>{{schedule.interval_type}}<br><br>child of ID: {{ schedule.parent }}</p> <i class="fas fa-pencil-alt"></i> Edit
{% else %} </button>
<h4>Interval</h4> <p>Cron String: {{schedule.cron_string}}</p> <button data-sch={{ schedule.schedule_id }} class="btn btn-danger del_button">
{% end %} <i class="fas fa-trash" aria-hidden="true"></i> Delete
</li> </button>
<li id="{{schedule.start_time}}" class="action" style="border-top: .1em solid gray;"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<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>
</div> </div>
{% end %} </div>
</tbody>
</table>
</div> </div>
</div> {% end %}
</tbody>
</table>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<style> <style>
.popover-body{ .popover-body {
color: white !important;; color: white !important;
} ;
}
</style> </style>
</div> </div>
<style> <style>
/* Hide scrollbar for Chrome, Safari and Opera */ /* Hide scrollbar for Chrome, Safari and Opera */
td::-webkit-scrollbar { td::-webkit-scrollbar {
display: none; display: none;
} }
/* Hide scrollbar for IE, Edge and Firefox */ /* Hide scrollbar for IE, Edge and Firefox */
td { td {
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none;
scrollbar-width: none; /* Firefox */ /* IE and Edge */
} scrollbar-width: none;
/* Firefox */
}
</style> </style>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
@ -236,138 +258,138 @@ td {
const serverId = new URLSearchParams(document.location.search).get('id') const serverId = new URLSearchParams(document.location.search).get('id')
$( document ).ready(function() { $(document).ready(function () {
console.log('ready for JS!') console.log('ready for JS!')
$('#schedule_table').DataTable({ $('#schedule_table').DataTable({
'order': [4, 'desc'] 'order': [4, 'desc']
} }
); );
}); });
$( document ).ready(function() { $(document).ready(function () {
console.log('ready for JS!') console.log('ready for JS!')
$('#mini_schedule_table').DataTable({ $('#mini_schedule_table').DataTable({
'order': [2, 'desc'] 'order': [2, 'desc']
} }
); );
document.getElementById('mini_schedule_table_wrapper').hidden = true; document.getElementById('mini_schedule_table_wrapper').hidden = true;
}); });
$(document).ready(function(){ $(document).ready(function () {
$('[data-toggle="popover"]').popover(); $('[data-toggle="popover"]').popover();
if($(window).width() < 1000){ if ($(window).width() < 1000) {
$('.too_small').popover("show"); $('.too_small').popover("show");
document.getElementById('schedule_table_wrapper').hidden = true; document.getElementById('schedule_table_wrapper').hidden = true;
document.getElementById('mini_schedule_table_wrapper').hidden = false; document.getElementById('mini_schedule_table_wrapper').hidden = false;
} }
}); });
$(window).ready(function(){ $(window).ready(function () {
$('body').click(function(){ $('body').click(function () {
$('.too_small').popover("hide"); $('.too_small').popover("hide");
}); });
}); });
$(window).resize(function() { $(window).resize(function () {
// This will execute whenever the window is resized // This will execute whenever the window is resized
if($(window).width() < 1000){ if ($(window).width() < 1000) {
$('.too_small').popover("show"); $('.too_small').popover("show");
document.getElementById('schedule_table_wrapper').hidden = true; document.getElementById('schedule_table_wrapper').hidden = true;
document.getElementById('mini_schedule_table_wrapper').hidden = false; document.getElementById('mini_schedule_table_wrapper').hidden = false;
} }
else{ else {
$('.too_small').popover("hide"); $('.too_small').popover("hide");
document.getElementById('schedule_table_wrapper').hidden = false; document.getElementById('schedule_table_wrapper').hidden = false;
document.getElementById('mini_schedule_table_wrapper').hidden = true; document.getElementById('mini_schedule_table_wrapper').hidden = true;
} // New width } // New width
}); });
</script> </script>
<script> <script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) { function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined; 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;
} }
$( 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) { function basicAdvanced(that) {
if (that.value == "advanced") { if (that.value == "advanced") {
document.getElementById("ifAdvanced").style.display = "block"; document.getElementById("ifAdvanced").style.display = "block";
document.getElementById("ifBasic").style.display = "none"; document.getElementById("ifBasic").style.display = "none";
document.getElementById("interval").required = false; document.getElementById("interval").required = false;
document.getElementById("time").required = false; document.getElementById("time").required = false;
} else { } else {
document.getElementById("ifAdvanced").style.display = "none"; document.getElementById("ifAdvanced").style.display = "none";
document.getElementById("ifBasic").style.display = "block"; document.getElementById("ifBasic").style.display = "block";
document.getElementById("interval").required = true; document.getElementById("interval").required = true;
document.getElementById("time").required = true; document.getElementById("time").required = true;
} }
} }
function ifDays(that) { function ifDays(that) {
if (that.value == "days") { if (that.value == "days") {
document.getElementById("ifDays").style.display = "block"; document.getElementById("ifDays").style.display = "block";
document.getElementById("time").required = true; document.getElementById("time").required = true;
} else { } else {
document.getElementById("ifDays").style.display = "none"; document.getElementById("ifDays").style.display = "none";
document.getElementById("time").required = false; document.getElementById("time").required = false;
}
} }
}
$( ".del_button" ).click(function() { $(".del_button").click(function () {
var sch_id = $(this).data('sch'); var sch_id = $(this).data('sch');
console.log(sch_id) console.log(sch_id)
bootbox.confirm({ bootbox.confirm({
title: "{{ translate('serverSchedules', 'areYouSure', data['lang']) }}", title: "{{ translate('serverSchedules', 'areYouSure', data['lang']) }}",
message: "{{ translate('serverSchedules', 'confirmDelete', data['lang']) }}", message: "{{ translate('serverSchedules', 'confirmDelete', data['lang']) }}",
buttons: { buttons: {
cancel: { cancel: {
label: '<i class="fas fa-times"></i> {{ translate("serverSchedules", "cancel", data['lang']) }}' label: '<i class="fas fa-times"></i> {{ translate("serverSchedules", "cancel", data['lang']) }}'
}, },
confirm: { confirm: {
className: 'btn-outline-danger', className: 'btn-outline-danger',
label: '<i class="fas fa-check"></i> {{ translate("serverSchedules", "confirm", data['lang']) }}' label: '<i class="fas fa-check"></i> {{ translate("serverSchedules", "confirm", data['lang']) }}'
} }
}, },
callback: function (result) { callback: function (result) {
console.log(result); console.log(result);
if (result == true) { if (result == true) {
del_task(sch_id, serverId); del_task(sch_id, serverId);
} }
} }
});
}); });
});
function del_task(sch_id, id){ function del_task(sch_id, id) {
var token = getCookie("_xsrf") var token = getCookie("_xsrf")
$.ajax({ $.ajax({
type: "DELETE", type: "DELETE",
headers: {'X-XSRFToken': token}, headers: { 'X-XSRFToken': token },
url: '/ajax/del_task?server_id='+id+'&schedule_id='+sch_id, url: '/ajax/del_task?server_id=' + id + '&schedule_id=' + sch_id,
data: { data: {
schedule_id: sch_id, schedule_id: sch_id,
id: id id: id
}, },
success: function(data) { success: function (data) {
location.reload(); location.reload();
}, },
}); });
} }
</script> </script>

View File

@ -31,7 +31,13 @@
<div class="col-sm-12 grid-margin"> <div class="col-sm-12 grid-margin">
<div class="card"> <div class="card">
<div class="card-body pt-0"> <div class="card-body pt-0">
{% include "parts/server_controls_list.html %}
<span class="d-none d-sm-block">
{% include "parts/server_controls_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_server_controls_list.html %}
</span>
<div class="col-md-12"> <div class="col-md-12">
<div class="input-group"> <div class="input-group">
@ -60,12 +66,12 @@
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div> </div>
{% elif data['downloading'] %} {% 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"> <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', <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> data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div> </div>
{% else %} {% else %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible"> <div id="control_buttons" 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, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
@ -130,16 +136,16 @@
webSocket.on('update_button_status', function (updateButton) { webSocket.on('update_button_status', function (updateButton) {
if (updateButton.isUpdating) { if (updateButton.isUpdating) {
if (updateButton.server_id == serverId) { if (updateButton.server_id == serverId) {
console.log(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>'; 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 { else {
if (updateButton.server_id == serverId) { if (updateButton.server_id == serverId) {
window.location.reload() window.location.reload()
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(serverId, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data["lang"]) }}</button><button onclick="send_command(serverId, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>'; document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(serverId, "start_server");" id="start-btn" 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 // Convert running to lower case (example: 'True' converts to 'true') and
@ -162,10 +168,10 @@
} }
if (webSocket) { if (webSocket) {
webSocket.on('send_start_reload', function () { webSocket.on('send_start_reload', function () {
location.reload() location.reload()
}); });
} }
//{% end %} //{% end %}
function get_server_log() { function get_server_log() {

View File

@ -55,7 +55,11 @@
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4); box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
} }
</style> </style>
{% if data['query'] %}
<form action="/public/login?{{ data['query'] }}" method="post">
{% else %}
<form action="/public/login" method="post"> <form action="/public/login" method="post">
{% end %}
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<div class="form-group"> <div class="form-group">
<label class="label">{{ translate('login', 'username', data['lang']) }}</label> <label class="label">{{ translate('login', 'username', data['lang']) }}</label>

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