Merge branch 'dev' into 'master'
Huge commit for Alpha 2.5/Some of 3 See merge request crafty-controller/crafty-commander!70
9
.dockerignore
Normal file
@ -0,0 +1,9 @@
|
||||
docker/
|
||||
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
crafty_commander.exe
|
||||
DBCHANGES.md
|
||||
docker-compose.yml.example
|
||||
README.md
|
4
.gitignore
vendored
@ -23,5 +23,9 @@ session.lock
|
||||
.header
|
||||
default.json
|
||||
.vscode/
|
||||
.DS_Store
|
||||
.DS_STORE
|
||||
|
||||
app/config/
|
||||
docker/*
|
||||
!docker/docker-compose.yml
|
||||
|
155
.gitlab-ci.yml
Normal file
@ -0,0 +1,155 @@
|
||||
stages:
|
||||
- win-dev
|
||||
- win-prod
|
||||
- docker-dev
|
||||
- docker-prod
|
||||
|
||||
docker-build-dev:
|
||||
image: docker:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--experimental"]
|
||||
stage: docker-dev
|
||||
tags:
|
||||
- docker
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'dev'
|
||||
environment:
|
||||
name: development
|
||||
before_script:
|
||||
- |
|
||||
apk --no-cache add curl
|
||||
latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p')
|
||||
echo "Using buildx version $latest_tag"
|
||||
curl -sSLo docker-buildx "https://github.com/docker/buildx/releases/download/$latest_tag/buildx-$latest_tag.linux-amd64"
|
||||
chmod a+x docker-buildx
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
|
||||
docker version
|
||||
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
tag=":$CI_COMMIT_REF_SLUG"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
- docker buildx create --use --name zedBuilder
|
||||
- docker buildx build
|
||||
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1
|
||||
--tag "$CI_REGISTRY_IMAGE${tag}"
|
||||
--platform linux/arm64/v8,linux/amd64
|
||||
--push .
|
||||
after_script:
|
||||
- |
|
||||
docker buildx rm zedBuilder && echo "Successfully Stopped builder instance" || echo "Failed to stop builder instance."
|
||||
echo "Please review multi-arch manifests are present:"
|
||||
docker buildx imagetools inspect "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
|
||||
|
||||
docker-build-prod:
|
||||
image: docker:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--experimental"]
|
||||
stage: docker-prod
|
||||
tags:
|
||||
- docker
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
environment:
|
||||
name: production
|
||||
before_script:
|
||||
- |
|
||||
apk --no-cache add curl
|
||||
latest_tag=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | sed -Ene '/^ *"tag_name": *"(v.+)",$/s//\1/p')
|
||||
echo "Using buildx version $latest_tag"
|
||||
curl -sSLo docker-buildx "https://github.com/docker/buildx/releases/download/$latest_tag/buildx-$latest_tag.linux-amd64"
|
||||
chmod a+x docker-buildx
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
mv docker-buildx ~/.docker/cli-plugins/docker-buildx
|
||||
docker version
|
||||
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
tag=""
|
||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||
- docker buildx create --use --name zedBuilder
|
||||
- docker buildx build
|
||||
--cache-from type=registry,ref="$CI_REGISTRY_IMAGE${tag}"
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1
|
||||
--tag "$CI_REGISTRY_IMAGE${tag}"
|
||||
--platform linux/arm64/v8,linux/amd64
|
||||
--push .
|
||||
after_script:
|
||||
- |
|
||||
docker buildx rm zedBuilder && echo "Successfully Stopped builder instance" || echo "Failed to stop builder instance."
|
||||
echo "Please review multi-arch manifests are present:"
|
||||
docker buildx imagetools inspect "$CI_REGISTRY_IMAGE${tag}"
|
||||
|
||||
win-dev-build:
|
||||
stage: win-dev
|
||||
tags:
|
||||
- win64
|
||||
cache:
|
||||
paths:
|
||||
- .venv/
|
||||
rules:
|
||||
- if: "$CI_COMMIT_BRANCH == 'dev'"
|
||||
environment:
|
||||
name: development
|
||||
script:
|
||||
- |
|
||||
$ErrorActionPreference = "Stop"
|
||||
py -m venv .venv
|
||||
.venv\Scripts\activate.ps1
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
- pyinstaller -F main.py
|
||||
--distpath .
|
||||
--icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico
|
||||
--name "crafty_commander"
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
artifacts:
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
exclude:
|
||||
- app\classes\**\*
|
||||
# Download latest:
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build
|
||||
|
||||
win-prod-build:
|
||||
stage: win-prod
|
||||
tags:
|
||||
- win64
|
||||
cache:
|
||||
paths:
|
||||
- .venv/
|
||||
rules:
|
||||
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
|
||||
environment:
|
||||
name: production
|
||||
script:
|
||||
- |
|
||||
$ErrorActionPreference = "Stop"
|
||||
py -m venv .venv
|
||||
.venv\Scripts\activate.ps1
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
- pyinstaller -F main.py
|
||||
--distpath .
|
||||
--icon app\frontend\static\assets\images\Crafty_4-0_Logo_square.ico
|
||||
--name "crafty_commander"
|
||||
--paths .venv\Lib\site-packages
|
||||
--hidden-import cryptography
|
||||
--hidden-import cffi
|
||||
artifacts:
|
||||
paths:
|
||||
- app\
|
||||
- .\crafty_commander.exe
|
||||
exclude:
|
||||
- app\classes\**\*
|
||||
# Download latest:
|
||||
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build
|
74
DBCHANGES.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Database change guide for contributors
|
||||
|
||||
When updating a database schema modify the schema in `app/classes/shared/models.py` and create a new migration with the `migrations add <name>` command (in Crafty's prompt).
|
||||
|
||||
A full list of helper functions you can find in `app/classes/shared/models.py`
|
||||
|
||||
## Example migration files
|
||||
|
||||
### Rename column/field
|
||||
|
||||
```py
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.rename_column('my_table', 'old_name', 'new_name') # First argument can be model class OR table name
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.rename_column('my_table', 'new_name', 'old_name') # First argument can be model class OR table name
|
||||
|
||||
```
|
||||
|
||||
### Rename table/model
|
||||
|
||||
```py
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.rename_table('old_name', 'new_name') # First argument can be model class OR table name
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.rename_table('new_name', 'old_name') # First argument can be model class OR table name
|
||||
|
||||
```
|
||||
|
||||
### Create table/model
|
||||
|
||||
```py
|
||||
import peewee
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
db = database
|
||||
#Copy Paste here the class of the New Table from models.py
|
||||
class NewTable(peewee.Model):
|
||||
my_id = peewee.IntegerField(unique=True, primary_key=True)
|
||||
|
||||
class Meta:
|
||||
table_name = 'new_table'
|
||||
database = db
|
||||
|
||||
migrator.create_table(NewTable)
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_table('new_table') # Can be model class OR table name
|
||||
|
||||
```
|
||||
|
||||
### Add columns/fields
|
||||
|
||||
```py
|
||||
import peewee
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('table_name', new_field_name=peewee.CharField(default="")) # First argument can be model class OR table name
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('table_name', ['new_field_name']) # First argument can be model class OR table name
|
||||
|
||||
```
|
32
Dockerfile
Normal file
@ -0,0 +1,32 @@
|
||||
FROM python:alpine
|
||||
|
||||
LABEL maintainer="Dockerfile created by Zedifus <https://gitlab.com/zedifus>"
|
||||
|
||||
# Install Packages, Build Dependencies & Garbage Collect & Harden
|
||||
# (Alpine Edge repo is needed because jre16 is new)
|
||||
COPY requirements.txt /commander/requirements.txt
|
||||
RUN apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/community llvm11-libs openssl-dev rust cargo gcc musl-dev libffi-dev make openjdk8-jre-base openjdk11-jre-headless openjdk16-jre-headless mariadb-dev \
|
||||
&& pip3 install --no-cache-dir -r /commander/requirements.txt \
|
||||
&& apk del --no-cache gcc musl-dev libffi-dev make rust cargo openssl-dev llvm11-libs \
|
||||
&& rm -rf /sbin/apk \
|
||||
&& rm -rf /etc/apk \
|
||||
&& rm -rf /lib/apk \
|
||||
&& rm -rf /usr/share/apk \
|
||||
&& rm -rf /var/lib/apk
|
||||
|
||||
# Copy Source & copy default config from image
|
||||
COPY ./ /commander
|
||||
WORKDIR /commander
|
||||
RUN mv ./app/config ./app/config_original \
|
||||
&& mv ./app/config_original/default.json.example ./app/config_original/default.json \
|
||||
&& chmod +x ./docker_launcher.sh
|
||||
|
||||
# Expose Web Interface port & Server port range
|
||||
EXPOSE 8000
|
||||
EXPOSE 8443
|
||||
EXPOSE 19132
|
||||
EXPOSE 25500-25600
|
||||
|
||||
# Start Crafty Commander through wrapper
|
||||
ENTRYPOINT ["/commander/docker_launcher.sh"]
|
||||
CMD ["-v", "-d", "-i"]
|
118
README.md
Normal file
@ -0,0 +1,118 @@
|
||||
# Crafty Controller 4.0.0-alpha.3
|
||||
> Python based Control Panel for your Minecraft Server
|
||||
|
||||
## What is Crafty Controller?
|
||||
Crafty Controller is a Minecraft Server Control Panel / Launcher. The purpose
|
||||
of Crafty Controller is to launch a Minecraft Server in the background and present
|
||||
a web interface for the server administrators to interact with their servers. Crafty
|
||||
is compatible with Docker, Linux, Windows 7, Windows 8 and Windows 10.
|
||||
|
||||
## Documentation
|
||||
Temporary documentation available on [GitLab](https://gitlab.com/crafty-controller/crafty-commander/wikis/home)
|
||||
|
||||
## Meta
|
||||
Project Homepage - https://craftycontrol.com
|
||||
|
||||
Discord Server - https://discord.gg/9VJPhCE
|
||||
|
||||
Git Repository - https://gitlab.com/crafty-controller/crafty-web
|
||||
|
||||
## Basic Docker Usage
|
||||
|
||||
**To get started with docker**, all you need to do is pull the image from this git repository's registry.
|
||||
This is done by using `docker-compose` or `docker run`(You don't need to clone the Repository and build, like in 3.x ).
|
||||
|
||||
If you have a config folder already from previous local installation or docker setup, the image should mount this volume, if none is present then it will populate its own config folder for you.
|
||||
|
||||
### 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.
|
||||
|
||||
The image is located at: `registry.gitlab.com/crafty-controller/crafty-commander:latest`
|
||||
| Branch | Status |
|
||||
| ----------------- | ------------------------------------------------------------------ |
|
||||
| :latest | [](https://gitlab.com/crafty-controller/crafty-commander/-/commits/master) |
|
||||
| :dev | [](https://gitlab.com/crafty-controller/crafty-commander/-/commits/dev) |
|
||||
|
||||
While the repository is still **private / pre-release**,
|
||||
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)
|
||||
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
|
||||
```yml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
crafty:
|
||||
container_name: crafty_commander
|
||||
image: registry.gitlab.com/crafty-controller/crafty-commander:latest
|
||||
ports:
|
||||
- "8000:8000" # HTTP
|
||||
- "8443:8443" # HTTPS
|
||||
- "8123:8123" # DYNMAP
|
||||
- "19132:19132/udp" # BEDROCK
|
||||
- "24000-25600:24000-25600" # MC SERV PORT RANGE
|
||||
volumes:
|
||||
- ./docker/backups:/commander/backups
|
||||
- ./docker/logs:/commander/logs
|
||||
- ./docker/servers:/commander/servers
|
||||
- ./docker/config:/commander/app/config
|
||||
```
|
||||
|
||||
#### docker run
|
||||
```sh
|
||||
$ docker run \
|
||||
--name crafty_commander \
|
||||
-p 8000:8000 \
|
||||
-p 8443:8443 \
|
||||
-p 8123:8123 \
|
||||
-p 19132:19132/udp \
|
||||
-p 24000-25600:24000-25600 \
|
||||
-v "/$(pwd)/docker/backups:/commander/backups" \
|
||||
-v "/$(pwd)/docker/logs:/commander/logs" \
|
||||
-v "/$(pwd)/docker/servers:/commander/servers" \
|
||||
-v "/$(pwd)/docker/config:/commander/app/config" \
|
||||
registry.gitlab.com/crafty-controller/crafty-commander:latest
|
||||
```
|
||||
|
||||
### Building from the cloned repository:
|
||||
|
||||
If you are building from `docker-compose` you can find the compose file in `./docker/docker-compose.yml` just `cd` to the docker directory and `docker-compose up -d`
|
||||
|
||||
If you'd rather not use `docker-compose` you can use the following `docker run`in the directory where the *Dockerfile* is:
|
||||
```sh
|
||||
# REMEMBER, Build your image first!
|
||||
$ docker build . -t crafty
|
||||
|
||||
$ docker run \
|
||||
--name crafty_commander \
|
||||
-p 8000:8000 \
|
||||
-p 8443:8443 \
|
||||
-p 8123:8123 \
|
||||
-p 19132:19132/udp \
|
||||
-p 24000-25600:24000-25600 \
|
||||
-v "/$(pwd)/docker/backups:/commander/backups" \
|
||||
-v "/$(pwd)/docker/logs:/commander/logs" \
|
||||
-v "/$(pwd)/docker/servers:/commander/servers" \
|
||||
-v "/$(pwd)/docker/config:/commander/app/config" \
|
||||
crafty
|
||||
```
|
||||
A fresh build will take several minutes depending on your system, but will be rapid there after.
|
72
app/classes/controllers/crafty_perms_controller.py
Normal file
@ -0,0 +1,72 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Crafty_Perms_Controller:
|
||||
|
||||
@staticmethod
|
||||
def list_defined_crafty_permissions():
|
||||
permissions_list = crafty_permissions.get_permissions_list()
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def get_mask_crafty_permissions(user_id):
|
||||
permissions_mask = crafty_permissions.get_crafty_permissions_mask(user_id)
|
||||
return permissions_mask
|
||||
|
||||
@staticmethod
|
||||
def set_permission(permission_mask, permission_tested: Enum_Permissions_Crafty, value):
|
||||
return crafty_permissions.set_permission(permission_mask, permission_tested, value)
|
||||
|
||||
@staticmethod
|
||||
def can_create_server(user_id):
|
||||
return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Server_Creation)
|
||||
|
||||
@staticmethod
|
||||
def can_add_user(user_id):
|
||||
#TODO: Complete if we need a User Addition limit
|
||||
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.User_Config)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def can_add_role(user_id):
|
||||
#TODO: Complete if we need a Role Addition limit
|
||||
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def list_all_crafty_permissions_quantity_limits():
|
||||
return crafty_permissions.get_all_permission_quantity_list()
|
||||
|
||||
@staticmethod
|
||||
def list_crafty_permissions_quantity_limits(user_id):
|
||||
return crafty_permissions.get_permission_quantity_list(user_id)
|
||||
|
||||
@staticmethod
|
||||
def get_crafty_permissions_list(user_id):
|
||||
permissions_mask = crafty_permissions.get_crafty_permissions_mask(user_id)
|
||||
permissions_list = crafty_permissions.get_permissions(permissions_mask)
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def add_server_creation(user_id):
|
||||
return crafty_permissions.add_server_creation(user_id)
|
115
app/classes/controllers/management_controller.py
Normal file
@ -0,0 +1,115 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.management import management_helper
|
||||
from app.classes.models.servers import servers_helper
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Management_Controller:
|
||||
|
||||
#************************************************************************************************
|
||||
# Host_Stats Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_latest_hosts_stats():
|
||||
return management_helper.get_latest_hosts_stats()
|
||||
|
||||
@staticmethod
|
||||
def new_api_token():
|
||||
return management_helper.new_api_token()
|
||||
|
||||
#************************************************************************************************
|
||||
# Commands Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_unactioned_commands():
|
||||
return management_helper.get_unactioned_commands()
|
||||
|
||||
@staticmethod
|
||||
def send_command(user_id, server_id, remote_ip, command):
|
||||
|
||||
server_name = servers_helper.get_server_friendly_name(server_id)
|
||||
|
||||
# Example: Admin issued command start_server for server Survival
|
||||
management_helper.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name),
|
||||
server_id, remote_ip)
|
||||
|
||||
management_helper.add_command(server_id, user_id, remote_ip, command)
|
||||
|
||||
@staticmethod
|
||||
def mark_command_complete(command_id=None):
|
||||
return management_helper.mark_command_complete(command_id)
|
||||
|
||||
#************************************************************************************************
|
||||
# Audit_Log Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_actity_log():
|
||||
return management_helper.get_actity_log()
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log(user_id, log_msg, server_id=None, source_ip=None):
|
||||
return management_helper.add_to_audit_log(user_id, log_msg, server_id, source_ip)
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip):
|
||||
return management_helper.add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip)
|
||||
|
||||
#************************************************************************************************
|
||||
# Schedules Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True):
|
||||
return management_helper.create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment, enabled)
|
||||
|
||||
@staticmethod
|
||||
def delete_scheduled_task(schedule_id):
|
||||
return management_helper.delete_scheduled_task(schedule_id)
|
||||
|
||||
@staticmethod
|
||||
def update_scheduled_task(schedule_id, updates):
|
||||
return management_helper.update_scheduled_task(schedule_id, updates)
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task(schedule_id):
|
||||
return management_helper.get_scheduled_task(schedule_id)
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_by_server(server_id):
|
||||
return management_helper.get_schedules_by_server(server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_all():
|
||||
return management_helper.get_schedules_all()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_enabled():
|
||||
return management_helper.get_schedules_enabled()
|
||||
|
||||
#************************************************************************************************
|
||||
# Backups Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_backup_config(server_id):
|
||||
return management_helper.get_backup_config(server_id)
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
return management_helper.set_backup_config(server_id, backup_path, max_backups, auto_enabled)
|
97
app/classes/controllers/roles_controller.py
Normal file
@ -0,0 +1,97 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.roles import roles_helper
|
||||
from app.classes.models.server_permissions import server_permissions
|
||||
from app.classes.models.users import users_helper
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Roles_Controller:
|
||||
|
||||
@staticmethod
|
||||
def get_all_roles():
|
||||
return roles_helper.get_all_roles()
|
||||
|
||||
@staticmethod
|
||||
def get_roleid_by_name(role_name):
|
||||
return roles_helper.get_roleid_by_name(role_name)
|
||||
|
||||
@staticmethod
|
||||
def get_role(role_id):
|
||||
return roles_helper.get_role(role_id)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def update_role(role_id, role_data={}, permissions_mask="00000000"):
|
||||
base_data = Roles_Controller.get_role_with_servers(role_id)
|
||||
up_data = {}
|
||||
added_servers = set()
|
||||
edited_servers = set()
|
||||
removed_servers = set()
|
||||
for key in role_data:
|
||||
if key == "role_id":
|
||||
continue
|
||||
elif key == "servers":
|
||||
added_servers = role_data['servers'].difference(base_data['servers'])
|
||||
removed_servers = base_data['servers'].difference(role_data['servers'])
|
||||
elif base_data[key] != role_data[key]:
|
||||
up_data[key] = role_data[key]
|
||||
up_data['last_update'] = helper.get_time_as_string()
|
||||
logger.debug("role: {} +server:{} -server{}".format(role_data, added_servers, removed_servers))
|
||||
for server in added_servers:
|
||||
server_permissions.get_or_create(role_id, server, permissions_mask)
|
||||
for server in base_data['servers']:
|
||||
server_permissions.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
|
||||
server_permissions.delete_roles_permissions(role_id, removed_servers)
|
||||
if up_data:
|
||||
roles_helper.update_role(role_id, up_data)
|
||||
|
||||
@staticmethod
|
||||
def add_role(role_name):
|
||||
return roles_helper.add_role(role_name)
|
||||
|
||||
@staticmethod
|
||||
def remove_role(role_id):
|
||||
role_data = Roles_Controller.get_role_with_servers(role_id)
|
||||
server_permissions.delete_roles_permissions(role_id, role_data['servers'])
|
||||
users_helper.remove_roles_from_role_id(role_id)
|
||||
return roles_helper.remove_role(role_id)
|
||||
|
||||
@staticmethod
|
||||
def role_id_exists(role_id):
|
||||
return roles_helper.role_id_exists(role_id)
|
||||
|
||||
@staticmethod
|
||||
def get_role_with_servers(role_id):
|
||||
role = roles_helper.get_role(role_id)
|
||||
|
||||
if role:
|
||||
servers_query = server_permissions.get_servers_from_role(role_id)
|
||||
# TODO: this query needs to be narrower
|
||||
servers = set()
|
||||
for s in servers_query:
|
||||
servers.add(s.server_id.server_id)
|
||||
role['servers'] = servers
|
||||
#logger.debug("role: ({}) {}".format(role_id, role))
|
||||
return role
|
||||
else:
|
||||
#logger.debug("role: ({}) {}".format(role_id, {}))
|
||||
return {}
|
95
app/classes/controllers/server_perms_controller.py
Normal file
@ -0,0 +1,95 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.main_models import db_helper
|
||||
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
|
||||
from app.classes.models.users import users_helper
|
||||
from app.classes.models.roles import roles_helper
|
||||
from app.classes.models.servers import servers_helper
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Server_Perms_Controller:
|
||||
|
||||
@staticmethod
|
||||
def list_defined_permissions():
|
||||
permissions_list = server_permissions.get_permissions_list()
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def get_mask_permissions(role_id, server_id):
|
||||
permissions_mask = server_permissions.get_permissions_mask(role_id, server_id)
|
||||
return permissions_mask
|
||||
|
||||
@staticmethod
|
||||
def get_role_permissions(role_id):
|
||||
permissions_list = server_permissions.get_role_permissions_list(role_id)
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def get_server_permissions_foruser(user_id, server_id):
|
||||
permissions_list = server_permissions.get_user_permissions_list(user_id, server_id)
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def add_role_server(server_id, role_id, rs_permissions="00000000"):
|
||||
return server_permissions.add_role_server(server_id, role_id, rs_permissions)
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Permissions Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_permissions_mask(role_id, server_id):
|
||||
return server_permissions.get_permissions_mask(role_id, server_id)
|
||||
|
||||
@staticmethod
|
||||
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value):
|
||||
return server_permissions.set_permission(permission_mask, permission_tested, value)
|
||||
|
||||
@staticmethod
|
||||
def get_role_permissions_list(role_id):
|
||||
return server_permissions.get_role_permissions_list(role_id)
|
||||
|
||||
@staticmethod
|
||||
def get_user_permissions_list(user_id, server_id):
|
||||
return server_permissions.get_user_permissions_list(user_id, server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats_from_roles(user_id):
|
||||
user_roles = users_helper.get_user_roles_id(user_id)
|
||||
roles_list = []
|
||||
role_server = []
|
||||
authorized_servers = []
|
||||
server_data = []
|
||||
|
||||
for u in user_roles:
|
||||
roles_list.append(roles_helper.get_role(u.role_id))
|
||||
|
||||
for r in roles_list:
|
||||
role_test = server_permissions.get_role_servers_from_role_id(r.get('role_id'))
|
||||
for t in role_test:
|
||||
role_server.append(t)
|
||||
|
||||
for s in role_server:
|
||||
authorized_servers.append(servers_helper.get_server_data_by_id(s.server_id))
|
||||
|
||||
for s in authorized_servers:
|
||||
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0]})
|
||||
return server_data
|
176
app/classes/controllers/servers_controller.py
Normal file
@ -0,0 +1,176 @@
|
||||
from app.classes.controllers.roles_controller import Roles_Controller
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import json
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.main_models import db_helper
|
||||
from app.classes.models.servers import servers_helper
|
||||
from app.classes.models.roles import roles_helper
|
||||
from app.classes.models.users import users_helper
|
||||
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.minecraft.stats import Stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Servers_Controller:
|
||||
|
||||
#************************************************************************************************
|
||||
# Generic Servers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
return servers_helper.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
|
||||
|
||||
@staticmethod
|
||||
def remove_server(server_id):
|
||||
roles_list = server_permissions.get_roles_from_server(server_id)
|
||||
for role in roles_list:
|
||||
role_id = role.role_id
|
||||
role_data = Roles_Controller.get_role_with_servers(role_id)
|
||||
role_data['servers'] = {server_id}
|
||||
server_permissions.delete_roles_permissions(role_id, role_data['servers'])
|
||||
server_permissions.remove_roles_of_server(server_id)
|
||||
servers_helper.remove_server(server_id)
|
||||
|
||||
@staticmethod
|
||||
def get_server_data_by_id(server_id):
|
||||
return servers_helper.get_server_data_by_id(server_id)
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_all_defined_servers():
|
||||
return servers_helper.get_all_defined_servers()
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers(user_id):
|
||||
server_data = []
|
||||
user_roles = users_helper.user_role_query(user_id)
|
||||
for us in user_roles:
|
||||
role_servers = server_permissions.get_role_servers_from_role_id(us.role_id)
|
||||
for role in role_servers:
|
||||
server_data.append(servers_helper.get_server_data_by_id(role.server_id))
|
||||
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_all_servers_stats():
|
||||
return servers_helper.get_all_servers_stats()
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats(user_id):
|
||||
server_data = []
|
||||
authorized_servers = Servers_Controller.get_authorized_servers(user_id)
|
||||
|
||||
for s in authorized_servers:
|
||||
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
|
||||
user_permissions = server_permissions.get_user_permissions_list(user_id, s.get('server_id'))
|
||||
if Enum_Permissions_Server.Commands in user_permissions:
|
||||
user_command_permission = True
|
||||
else:
|
||||
user_command_permission = False
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":user_command_permission})
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_server_friendly_name(server_id):
|
||||
return servers_helper.get_server_friendly_name(server_id)
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers_Stats Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_server_stats_by_id(server_id):
|
||||
return servers_helper.get_server_stats_by_id(server_id)
|
||||
|
||||
@staticmethod
|
||||
def server_id_exists(server_id):
|
||||
return servers_helper.server_id_exists(server_id)
|
||||
|
||||
@staticmethod
|
||||
def server_id_authorized(serverId, user_id):
|
||||
authorized = 0
|
||||
user_roles = users_helper.user_role_query(user_id)
|
||||
for role in user_roles:
|
||||
authorized = server_permissions.get_role_servers_from_role_id(role.role_id)
|
||||
|
||||
#authorized = db_helper.return_rows(authorized)
|
||||
|
||||
if authorized.count() == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def set_update(server_id, value):
|
||||
return servers_helper.set_update(server_id, value)
|
||||
|
||||
@staticmethod
|
||||
def get_TTL_without_player(server_id):
|
||||
return servers_helper.get_TTL_without_player(server_id)
|
||||
|
||||
@staticmethod
|
||||
def can_stop_no_players(server_id, time_limit):
|
||||
return servers_helper.can_stop_no_players(server_id, time_limit)
|
||||
|
||||
@staticmethod
|
||||
def set_waiting_start(server_id, value):
|
||||
servers_helper.set_waiting_start(server_id, value)
|
||||
|
||||
@staticmethod
|
||||
def get_waiting_start(server_id):
|
||||
return servers_helper.get_waiting_start(server_id)
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Helpers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_banned_players(server_id):
|
||||
stats = servers_helper.get_server_stats_by_id(server_id)
|
||||
server_path = stats['server_id']['path']
|
||||
path = os.path.join(server_path, 'banned-players.json')
|
||||
|
||||
try:
|
||||
with open(path) as file:
|
||||
content = file.read()
|
||||
file.close()
|
||||
except Exception as ex:
|
||||
print (ex)
|
||||
return None
|
||||
|
||||
return json.loads(content)
|
||||
|
||||
def check_for_old_logs(self):
|
||||
servers = servers_helper.get_all_defined_servers()
|
||||
for server in servers:
|
||||
logs_path = os.path.split(server['log_path'])[0]
|
||||
latest_log_file = os.path.split(server['log_path'])[1]
|
||||
logs_delete_after = int(server['logs_delete_after'])
|
||||
if logs_delete_after == 0:
|
||||
continue
|
||||
|
||||
log_files = list(filter(
|
||||
lambda val: val != latest_log_file,
|
||||
os.listdir(logs_path)
|
||||
))
|
||||
for log_file in log_files:
|
||||
log_file_path = os.path.join(logs_path, log_file)
|
||||
if self.check_file_exists(log_file_path) and \
|
||||
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
|
||||
os.remove(log_file_path)
|
||||
|
130
app/classes/controllers/users_controller.py
Normal file
@ -0,0 +1,130 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.users import Users, users_helper
|
||||
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
|
||||
from app.classes.models.management import management_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Users_Controller:
|
||||
|
||||
#************************************************************************************************
|
||||
# Users Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_all_users():
|
||||
return users_helper.get_all_users()
|
||||
|
||||
@staticmethod
|
||||
def get_id_by_name(username):
|
||||
return users_helper.get_user_id_by_name(username)
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_api_token(token: str):
|
||||
return users_helper.get_user_by_api_token(token)
|
||||
|
||||
@staticmethod
|
||||
def get_user_lang_by_id(user_id):
|
||||
return users_helper.get_user_lang_by_id(user_id)
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_id(user_id):
|
||||
return users_helper.get_user(user_id)
|
||||
|
||||
@staticmethod
|
||||
def user_query(user_id):
|
||||
return users_helper.user_query(user_id)
|
||||
|
||||
@staticmethod
|
||||
def update_user(user_id, user_data={}, user_crafty_data={}):
|
||||
base_data = users_helper.get_user(user_id)
|
||||
up_data = {}
|
||||
added_roles = set()
|
||||
removed_roles = set()
|
||||
removed_servers = set()
|
||||
for key in user_data:
|
||||
if key == "user_id":
|
||||
continue
|
||||
elif key == "roles":
|
||||
added_roles = user_data['roles'].difference(base_data['roles'])
|
||||
removed_roles = base_data['roles'].difference(user_data['roles'])
|
||||
elif key == "regen_api":
|
||||
if user_data['regen_api']:
|
||||
up_data['api_token'] = management_helper.new_api_token()
|
||||
elif key == "password":
|
||||
if user_data['password'] is not None and user_data['password'] != "":
|
||||
up_data['password'] = helper.encode_pass(user_data['password'])
|
||||
elif base_data[key] != user_data[key]:
|
||||
up_data[key] = user_data[key]
|
||||
up_data['last_update'] = helper.get_time_as_string()
|
||||
up_data['lang'] = user_data['lang']
|
||||
logger.debug("user: {} +role:{} -role:{}".format(user_data, added_roles, removed_roles))
|
||||
for role in added_roles:
|
||||
users_helper.get_or_create(user_id=user_id, role_id=role)
|
||||
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
|
||||
|
||||
for key in user_crafty_data:
|
||||
if key == "permissions_mask":
|
||||
permissions_mask = user_crafty_data['permissions_mask']
|
||||
if key == "server_quantity":
|
||||
limit_server_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Server_Creation.name]
|
||||
limit_user_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.User_Config.name]
|
||||
limit_role_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Roles_Config.name]
|
||||
else:
|
||||
limit_server_creation = 0
|
||||
limit_user_creation = 0
|
||||
limit_role_creation = 0
|
||||
|
||||
crafty_permissions.add_or_update_user(user_id, permissions_mask, limit_server_creation, limit_user_creation, limit_role_creation)
|
||||
|
||||
users_helper.delete_user_roles(user_id, removed_roles)
|
||||
|
||||
users_helper.update_user(user_id, up_data)
|
||||
|
||||
@staticmethod
|
||||
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
|
||||
return users_helper.add_user(username, password=password, api_token=api_token, enabled=enabled, superuser=superuser)
|
||||
|
||||
@staticmethod
|
||||
def remove_user(user_id):
|
||||
return users_helper.remove_user(user_id)
|
||||
|
||||
@staticmethod
|
||||
def user_id_exists(user_id):
|
||||
return users_helper.user_id_exists(user_id)
|
||||
|
||||
#************************************************************************************************
|
||||
# User Roles Methods
|
||||
#************************************************************************************************
|
||||
|
||||
@staticmethod
|
||||
def get_user_roles_id(user_id):
|
||||
return users_helper.get_user_roles_id(user_id)
|
||||
|
||||
@staticmethod
|
||||
def get_user_roles_names(user_id):
|
||||
return users_helper.get_user_roles_names(user_id)
|
||||
|
||||
@staticmethod
|
||||
def add_role_to_user(user_id, role_id):
|
||||
return users_helper.add_role_to_user(user_id, role_id)
|
||||
|
||||
@staticmethod
|
||||
def add_user_roles(user):
|
||||
return users_helper.add_user_roles(user)
|
||||
|
||||
@staticmethod
|
||||
def user_role_query(user_id):
|
||||
return users_helper.user_role_query(user_id)
|
@ -1,9 +1,12 @@
|
||||
from app.classes.shared.helpers import Helpers
|
||||
import struct
|
||||
import socket
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import logging.config
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -25,8 +28,26 @@ class Server:
|
||||
description = self.description
|
||||
if 'extra' in description.keys():
|
||||
for e in description['extra']:
|
||||
#Conversion format code needed only for Java Version
|
||||
lines.append(get_code_format("reset"))
|
||||
if "bold" in e.keys():
|
||||
lines.append(get_code_format("bold"))
|
||||
if "italic" in e.keys():
|
||||
lines.append(get_code_format("italic"))
|
||||
if "underlined" in e.keys():
|
||||
lines.append(get_code_format("underlined"))
|
||||
if "strikethrough" in e.keys():
|
||||
lines.append(get_code_format("strikethrough"))
|
||||
if "obfuscated" in e.keys():
|
||||
lines.append(get_code_format("obfuscated"))
|
||||
if "color" in e.keys():
|
||||
lines.append(get_code_format(e['color']))
|
||||
#Then append the text
|
||||
if "text" in e.keys():
|
||||
lines.append(e['text'])
|
||||
if e['text'] == '\n':
|
||||
lines.append("§§")
|
||||
else:
|
||||
lines.append(e['text'])
|
||||
|
||||
total_text = " ".join(lines)
|
||||
self.description = total_text
|
||||
@ -70,6 +91,26 @@ class Player:
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_code_format(format_name):
|
||||
root_dir = os.path.abspath(os.path.curdir)
|
||||
format_file = os.path.join(root_dir, 'app', 'config', 'motd_format.json')
|
||||
try:
|
||||
with open(format_file, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if format_name in data.keys():
|
||||
return data.get(format_name)
|
||||
else:
|
||||
logger.error("Format MOTD Error: format name {} does not exist".format(format_name))
|
||||
console.error("Format MOTD Error: format name {} does not exist".format(format_name))
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
logger.critical("Config File Error: Unable to read {} due to {}".format(format_file, e))
|
||||
console.critical("Config File Error: Unable to read {} due to {}".format(format_file, e))
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
# For the rest of requests see wiki.vg/Protocol
|
||||
def ping(ip, port):
|
||||
|
@ -19,7 +19,7 @@ class ServerProps:
|
||||
s = line
|
||||
s1 = s[:s.find('=')]
|
||||
if '\n' in s:
|
||||
s2 = s[s.find('=')+1:s.find('\\')]
|
||||
s2 = s[s.find('=')+1:s.find('\n')]
|
||||
else:
|
||||
s2 = s[s.find('=')+1:]
|
||||
d[s1] = s2
|
||||
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
import shutil
|
||||
import logging
|
||||
@ -8,8 +9,9 @@ from datetime import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Servers
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -17,8 +19,8 @@ try:
|
||||
import requests
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -71,6 +73,29 @@ class ServerJars:
|
||||
data = self._read_cache()
|
||||
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):
|
||||
logger.info("Checking serverjars.com API status")
|
||||
|
||||
@ -149,7 +174,11 @@ class ServerJars:
|
||||
response = self._get_api_result(url)
|
||||
return response
|
||||
|
||||
def download_jar(self, server, version, path):
|
||||
def download_jar(self, server, version, path, name):
|
||||
update_thread = threading.Thread(target=self.a_download_jar, daemon=True, name="exe_download", args=(server, version, path, name))
|
||||
update_thread.start()
|
||||
|
||||
def a_download_jar(self, server, version, path, name):
|
||||
fetch_url = "{base}/api/fetchJar/{server}/{version}".format(base=self.base_url, server=server, version=version)
|
||||
|
||||
# open a file stream
|
||||
@ -161,6 +190,8 @@ class ServerJars:
|
||||
except Exception as e:
|
||||
logger.error("Unable to save jar to {path} due to error:{error}".format(path=path, error=e))
|
||||
pass
|
||||
websocket_helper.broadcast('notification', "Executable download finished for server named: " + name)
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
@ -4,12 +4,13 @@ import time
|
||||
import psutil
|
||||
import logging
|
||||
import datetime
|
||||
import base64
|
||||
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.minecraft.mc_ping import ping
|
||||
from app.classes.shared.models import db_helper
|
||||
from app.classes.shared.models import Host_Stats, Server_Stats
|
||||
from app.classes.models.management import Host_Stats
|
||||
from app.classes.models.servers import Server_Stats, servers_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -44,15 +45,16 @@ class Stats:
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _get_process_stats(process_pid: int):
|
||||
if process_pid is None:
|
||||
def _get_process_stats(process):
|
||||
if process is None:
|
||||
process_stats = {
|
||||
'cpu_usage': 0,
|
||||
'memory_usage': 0,
|
||||
'mem_percentage': 0
|
||||
}
|
||||
return process_stats
|
||||
|
||||
else:
|
||||
process_pid = process.pid
|
||||
try:
|
||||
p = psutil.Process(process_pid)
|
||||
dummy = p.cpu_percent()
|
||||
@ -145,19 +147,26 @@ class Stats:
|
||||
logger.info("Unable to read json from ping_obj: {}".format(e))
|
||||
pass
|
||||
|
||||
try:
|
||||
server_icon = base64.encodebytes(ping_obj.icon)
|
||||
except Exception as e:
|
||||
server_icon = False
|
||||
logger.info("Unable to read the server icon : {}".format(e))
|
||||
|
||||
ping_data = {
|
||||
'online': online_stats.get("online", 0),
|
||||
'max': online_stats.get('max', 0),
|
||||
'players': online_stats.get('players', 0),
|
||||
'server_description': ping_obj.description,
|
||||
'server_version': ping_obj.version
|
||||
'server_version': ping_obj.version,
|
||||
'server_icon': server_icon
|
||||
}
|
||||
|
||||
return ping_data
|
||||
|
||||
def get_server_players(self, server_id):
|
||||
|
||||
server = db_helper.get_server_data_by_id(server_id)
|
||||
server = servers_helper.get_server_data_by_id(server_id)
|
||||
|
||||
logger.info("Getting players for server {}".format(server))
|
||||
|
||||
@ -167,8 +176,8 @@ class Stats:
|
||||
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
internal_ip = server_data.get('server-ip', "127.0.0.1")
|
||||
server_port = server_settings.get('server-port', "25565")
|
||||
internal_ip = server['server_ip']
|
||||
server_port = server['server_port']
|
||||
|
||||
logger.debug("Pinging {} on port {}".format(internal_ip, server_port))
|
||||
int_mc_ping = ping(internal_ip, int(server_port))
|
||||
@ -193,6 +202,8 @@ class Stats:
|
||||
for s in servers:
|
||||
|
||||
server_id = s.get('server_id', None)
|
||||
server = servers_helper.get_server_data_by_id(server_id)
|
||||
|
||||
|
||||
logger.debug('Getting stats for server: {}'.format(server_id))
|
||||
|
||||
@ -200,18 +211,18 @@ class Stats:
|
||||
server_obj = s.get('server_obj', None)
|
||||
server_obj.reload_server_settings()
|
||||
server_settings = s.get('server_settings', {})
|
||||
server_data = s.get('server_data_obj', {})
|
||||
server_data = self.controller.get_server_data(server_id)
|
||||
|
||||
# world data
|
||||
world_name = server_settings.get('level-name', 'Unknown')
|
||||
world_path = os.path.join(server_data.get('path', None), world_name)
|
||||
|
||||
# process stats
|
||||
p_stats = self._get_process_stats(server_obj.PID)
|
||||
p_stats = self._get_process_stats(server_obj.process)
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
internal_ip = server_data.get('server-ip', "127.0.0.1")
|
||||
server_port = server_settings.get('server-port', "25565")
|
||||
internal_ip = server['server_ip']
|
||||
server_port = server['server_port']
|
||||
|
||||
logger.debug("Pinging server '{}' on {}:{}".format(s.get('server_name', "ID#{}".format(server_id)), internal_ip, server_port))
|
||||
int_mc_ping = ping(internal_ip, int(server_port))
|
||||
@ -246,6 +257,65 @@ class Stats:
|
||||
server_stats_list.append(server_stats)
|
||||
|
||||
return server_stats_list
|
||||
|
||||
def get_raw_server_stats(self, server_id):
|
||||
|
||||
server_stats = {}
|
||||
server = self.controller.get_server_obj(server_id)
|
||||
|
||||
logger.debug('Getting stats for server: {}'.format(server_id))
|
||||
|
||||
# get our server object, settings and data dictionaries
|
||||
server_obj = self.controller.get_server_obj(server_id)
|
||||
server_obj.reload_server_settings()
|
||||
server_settings = self.controller.get_server_settings(server_id)
|
||||
server_data = self.controller.get_server_data(server_id)
|
||||
|
||||
# world data
|
||||
world_name = server_settings.get('level-name', 'Unknown')
|
||||
world_path = os.path.join(server_data.get('path', None), world_name)
|
||||
|
||||
# process stats
|
||||
p_stats = self._get_process_stats(server_obj.process)
|
||||
|
||||
# TODO: search server properties file for possible override of 127.0.0.1
|
||||
#internal_ip = server['server_ip']
|
||||
#server_port = server['server_port']
|
||||
internal_ip = server_data.get('server_ip', "127.0.0.1")
|
||||
server_port = server_settings.get('server-port', "25565")
|
||||
|
||||
|
||||
logger.debug("Pinging server '{}' on {}:{}".format(server.name, internal_ip, server_port))
|
||||
int_mc_ping = ping(internal_ip, int(server_port))
|
||||
|
||||
int_data = False
|
||||
ping_data = {}
|
||||
|
||||
# if we got a good ping return, let's parse it
|
||||
if int_mc_ping:
|
||||
int_data = True
|
||||
ping_data = self.parse_server_ping(int_mc_ping)
|
||||
|
||||
server_stats = {
|
||||
'id': server_id,
|
||||
'started': server_obj.get_start_time(),
|
||||
'running': server_obj.check_running(),
|
||||
'cpu': p_stats.get('cpu_usage', 0),
|
||||
'mem': p_stats.get('memory_usage', 0),
|
||||
"mem_percent": p_stats.get('mem_percentage', 0),
|
||||
'world_name': world_name,
|
||||
'world_size': self.get_world_size(world_path),
|
||||
'server_port': server_port,
|
||||
'int_ping_results': int_data,
|
||||
'online': ping_data.get("online", False),
|
||||
"max": ping_data.get("max", False),
|
||||
'players': ping_data.get("players", False),
|
||||
'desc': ping_data.get("server_description", False),
|
||||
'version': ping_data.get("server_version", False),
|
||||
'icon': ping_data.get("server_icon", False)
|
||||
}
|
||||
|
||||
return server_stats
|
||||
|
||||
def record_stats(self):
|
||||
stats_to_send = self.get_node_stats()
|
||||
|
194
app/classes/models/crafty_permissions.py
Normal file
@ -0,0 +1,194 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.users import Users
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# User_Crafty Class
|
||||
#************************************************************************************************
|
||||
class User_Crafty(Model):
|
||||
user_id = ForeignKeyField(Users, backref='users_crafty')
|
||||
permissions = CharField(default="00000000")
|
||||
limit_server_creation = IntegerField(default=-1)
|
||||
limit_user_creation = IntegerField(default=0)
|
||||
limit_role_creation = IntegerField(default=0)
|
||||
created_server = IntegerField(default=0)
|
||||
created_user = IntegerField(default=0)
|
||||
created_role = IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_crafty'
|
||||
database = database
|
||||
|
||||
#************************************************************************************************
|
||||
# Crafty Permissions Class
|
||||
#************************************************************************************************
|
||||
class Enum_Permissions_Crafty(Enum):
|
||||
Server_Creation = 0
|
||||
User_Config = 1
|
||||
Roles_Config = 2
|
||||
|
||||
class Permissions_Crafty:
|
||||
|
||||
#************************************************************************************************
|
||||
# Crafty Permissions Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_permissions_list():
|
||||
permissions_list = []
|
||||
for member in Enum_Permissions_Crafty.__members__.items():
|
||||
permissions_list.append(member[1])
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def get_permissions(permissions_mask):
|
||||
permissions_list = []
|
||||
for member in Enum_Permissions_Crafty.__members__.items():
|
||||
if crafty_permissions.has_permission(permissions_mask, member[1]):
|
||||
permissions_list.append(member[1])
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def has_permission(permission_mask, permission_tested: Enum_Permissions_Crafty):
|
||||
result = False
|
||||
if permission_mask[permission_tested.value] == '1':
|
||||
result = True
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def set_permission(permission_mask, permission_tested: Enum_Permissions_Crafty, value):
|
||||
l = list(permission_mask)
|
||||
l[permission_tested.value] = str(value)
|
||||
permission_mask = ''.join(l)
|
||||
return permission_mask
|
||||
|
||||
@staticmethod
|
||||
def get_permission(permission_mask, permission_tested: Enum_Permissions_Crafty):
|
||||
return permission_mask[permission_tested.value]
|
||||
|
||||
@staticmethod
|
||||
def get_crafty_permissions_mask(user_id):
|
||||
permissions_mask = ''
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
permissions_mask = user_crafty.permissions
|
||||
return permissions_mask
|
||||
|
||||
@staticmethod
|
||||
def get_all_permission_quantity_list():
|
||||
quantity_list = {
|
||||
Enum_Permissions_Crafty.Server_Creation.name: -1,
|
||||
Enum_Permissions_Crafty.User_Config.name: -1,
|
||||
Enum_Permissions_Crafty.Roles_Config.name: -1,
|
||||
}
|
||||
return quantity_list
|
||||
|
||||
@staticmethod
|
||||
def get_permission_quantity_list(user_id):
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
quantity_list = {
|
||||
Enum_Permissions_Crafty.Server_Creation.name: user_crafty.limit_server_creation,
|
||||
Enum_Permissions_Crafty.User_Config.name: user_crafty.limit_user_creation,
|
||||
Enum_Permissions_Crafty.Roles_Config.name: user_crafty.limit_role_creation,
|
||||
}
|
||||
return quantity_list
|
||||
|
||||
#************************************************************************************************
|
||||
# User_Crafty Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_User_Crafty(user_id):
|
||||
try:
|
||||
user_crafty = User_Crafty.select().where(User_Crafty.user_id == user_id).get()
|
||||
except User_Crafty.DoesNotExist:
|
||||
user_crafty = User_Crafty.insert({
|
||||
User_Crafty.user_id: user_id,
|
||||
User_Crafty.permissions: "000",
|
||||
User_Crafty.limit_server_creation: 0,
|
||||
User_Crafty.limit_user_creation: 0,
|
||||
User_Crafty.limit_role_creation: 0,
|
||||
User_Crafty.created_server: 0,
|
||||
User_Crafty.created_user: 0,
|
||||
User_Crafty.created_role: 0,
|
||||
}).execute()
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
return user_crafty
|
||||
|
||||
@staticmethod
|
||||
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()
|
||||
return user_crafty
|
||||
|
||||
@staticmethod
|
||||
def add_or_update_user(user_id, permissions_mask, limit_server_creation, limit_user_creation, limit_role_creation):
|
||||
try:
|
||||
user_crafty = User_Crafty.select().where(User_Crafty.user_id == user_id).get()
|
||||
user_crafty.permissions = permissions_mask
|
||||
user_crafty.limit_server_creation = limit_server_creation
|
||||
user_crafty.limit_user_creation = limit_user_creation
|
||||
user_crafty.limit_role_creation = limit_role_creation
|
||||
User_Crafty.save(user_crafty)
|
||||
except:
|
||||
User_Crafty.insert({
|
||||
User_Crafty.user_id: user_id,
|
||||
User_Crafty.permissions: permissions_mask,
|
||||
User_Crafty.limit_server_creation: limit_server_creation,
|
||||
User_Crafty.limit_user_creation: limit_user_creation,
|
||||
User_Crafty.limit_role_creation: limit_role_creation
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_created_quantity_list(user_id):
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
quantity_list = {
|
||||
Enum_Permissions_Crafty.Server_Creation.name: user_crafty.created_server,
|
||||
Enum_Permissions_Crafty.User_Config.name: user_crafty.created_user,
|
||||
Enum_Permissions_Crafty.Roles_Config.name: user_crafty.created_role,
|
||||
}
|
||||
return quantity_list
|
||||
|
||||
@staticmethod
|
||||
def get_crafty_limit_value(user_id, permission):
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
quantity_list = crafty_permissions.get_permission_quantity_list(user_id)
|
||||
return quantity_list[permission]
|
||||
|
||||
@staticmethod
|
||||
def can_add_in_crafty(user_id, permission):
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
can = crafty_permissions.has_permission(user_crafty.permissions, permission)
|
||||
limit_list = crafty_permissions.get_permission_quantity_list(user_id)
|
||||
quantity_list = crafty_permissions.get_created_quantity_list(user_id)
|
||||
return can and ((quantity_list[permission.name] < limit_list[permission.name]) or limit_list[permission.name] == -1 )
|
||||
|
||||
@staticmethod
|
||||
def add_server_creation(user_id):
|
||||
user_crafty = crafty_permissions.get_User_Crafty(user_id)
|
||||
user_crafty.created_server += 1
|
||||
User_Crafty.save(user_crafty)
|
||||
return user_crafty.created_server
|
||||
|
||||
crafty_permissions = Permissions_Crafty()
|
317
app/classes/models/management.py
Normal file
@ -0,0 +1,317 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import db_helper
|
||||
from app.classes.models.users import Users, users_helper
|
||||
from app.classes.models.servers import Servers, servers_helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
#************************************************************************************************
|
||||
# Audit_Log Class
|
||||
#************************************************************************************************
|
||||
class Audit_Log(Model):
|
||||
audit_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
user_name = CharField(default="")
|
||||
user_id = IntegerField(default=0, index=True)
|
||||
source_ip = CharField(default='127.0.0.1')
|
||||
server_id = IntegerField(default=None, index=True) # When auditing global events, use server ID 0
|
||||
log_msg = TextField(default='')
|
||||
|
||||
class Meta:
|
||||
database = database
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Host_Stats Class
|
||||
#************************************************************************************************
|
||||
class Host_Stats(Model):
|
||||
time = DateTimeField(default=datetime.datetime.now, index=True)
|
||||
boot_time = CharField(default="")
|
||||
cpu_usage = FloatField(default=0)
|
||||
cpu_cores = IntegerField(default=0)
|
||||
cpu_cur_freq = FloatField(default=0)
|
||||
cpu_max_freq = FloatField(default=0)
|
||||
mem_percent = FloatField(default=0)
|
||||
mem_usage = CharField(default="")
|
||||
mem_total = CharField(default="")
|
||||
disk_json = TextField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = "host_stats"
|
||||
database = database
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Commands Class
|
||||
#************************************************************************************************
|
||||
class Commands(Model):
|
||||
command_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_id = ForeignKeyField(Servers, backref='server', index=True)
|
||||
user = ForeignKeyField(Users, backref='user', index=True)
|
||||
source_ip = CharField(default='127.0.0.1')
|
||||
command = CharField(default='')
|
||||
executed = BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "commands"
|
||||
database = database
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Webhooks Class
|
||||
#************************************************************************************************
|
||||
class Webhooks(Model):
|
||||
id = AutoField()
|
||||
name = CharField(max_length=64, unique=True, index=True)
|
||||
method = CharField(default="POST")
|
||||
url = CharField(unique=True)
|
||||
event = CharField(default="")
|
||||
send_data = BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "webhooks"
|
||||
database = database
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Schedules Class
|
||||
#************************************************************************************************
|
||||
class Schedules(Model):
|
||||
schedule_id = IntegerField(unique=True, primary_key=True)
|
||||
server_id = ForeignKeyField(Servers, backref='schedule_server')
|
||||
enabled = BooleanField()
|
||||
action = CharField()
|
||||
interval = IntegerField()
|
||||
interval_type = CharField()
|
||||
start_time = CharField(null=True)
|
||||
command = CharField(null=True)
|
||||
comment = CharField()
|
||||
|
||||
class Meta:
|
||||
table_name = 'schedules'
|
||||
database = database
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Backups Class
|
||||
#************************************************************************************************
|
||||
class Backups(Model):
|
||||
directories = CharField(null=True)
|
||||
max_backups = IntegerField()
|
||||
server_id = ForeignKeyField(Servers, backref='backups_server')
|
||||
schedule_id = ForeignKeyField(Schedules, backref='backups_schedule')
|
||||
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
database = database
|
||||
|
||||
class helpers_management:
|
||||
|
||||
#************************************************************************************************
|
||||
# Host_Stats Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_latest_hosts_stats():
|
||||
query = Host_Stats.select().order_by(Host_Stats.id.desc()).get()
|
||||
return model_to_dict(query)
|
||||
|
||||
#************************************************************************************************
|
||||
# Commands Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def add_command(server_id, user_id, remote_ip, command):
|
||||
Commands.insert({
|
||||
Commands.server_id: server_id,
|
||||
Commands.user: user_id,
|
||||
Commands.source_ip: remote_ip,
|
||||
Commands.command: command
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_unactioned_commands():
|
||||
query = Commands.select().where(Commands.executed == 0)
|
||||
return db_helper.return_rows(query)
|
||||
|
||||
@staticmethod
|
||||
def mark_command_complete(command_id=None):
|
||||
if command_id is not None:
|
||||
logger.debug("Marking Command {} completed".format(command_id))
|
||||
Commands.update({
|
||||
Commands.executed: True
|
||||
}).where(Commands.command_id == command_id).execute()
|
||||
|
||||
#************************************************************************************************
|
||||
# Audit_Log Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_actity_log():
|
||||
q = Audit_Log.select()
|
||||
return db_helper.return_db_rows(q)
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log(user_id, log_msg, server_id=None, source_ip=None):
|
||||
logger.debug("Adding to audit log User:{} - Message: {} ".format(user_id, log_msg))
|
||||
user_data = users_helper.get_user(user_id)
|
||||
|
||||
audit_msg = "{} {}".format(str(user_data['username']).capitalize(), log_msg)
|
||||
|
||||
websocket_helper.broadcast('notification', audit_msg)
|
||||
|
||||
Audit_Log.insert({
|
||||
Audit_Log.user_name: user_data['username'],
|
||||
Audit_Log.user_id: user_id,
|
||||
Audit_Log.server_id: server_id,
|
||||
Audit_Log.log_msg: audit_msg,
|
||||
Audit_Log.source_ip: source_ip
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip):
|
||||
Audit_Log.insert({
|
||||
Audit_Log.user_name: user_name,
|
||||
Audit_Log.user_id: user_id,
|
||||
Audit_Log.server_id: server_id,
|
||||
Audit_Log.log_msg: log_msg,
|
||||
Audit_Log.source_ip: source_ip
|
||||
}).execute()
|
||||
|
||||
#************************************************************************************************
|
||||
# Schedules Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True):
|
||||
sch_id = Schedules.insert({
|
||||
Schedules.server_id: server_id,
|
||||
Schedules.action: action,
|
||||
Schedules.enabled: enabled,
|
||||
Schedules.interval: interval,
|
||||
Schedules.interval_type: interval_type,
|
||||
Schedules.start_time: start_time,
|
||||
Schedules.command: command,
|
||||
Schedules.comment: comment
|
||||
}).execute()
|
||||
return sch_id
|
||||
|
||||
@staticmethod
|
||||
def delete_scheduled_task(schedule_id):
|
||||
sch = Schedules.get(Schedules.schedule_id == schedule_id)
|
||||
return Schedules.delete_instance(sch)
|
||||
|
||||
@staticmethod
|
||||
def update_scheduled_task(schedule_id, updates):
|
||||
Schedules.update(updates).where(Schedules.schedule_id == schedule_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task(schedule_id):
|
||||
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_by_server(server_id):
|
||||
return Schedules.select().where(Schedules.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_all():
|
||||
return Schedules.select().execute()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_enabled():
|
||||
return Schedules.select().where(Schedules.enabled == True).execute()
|
||||
|
||||
#************************************************************************************************
|
||||
# Backups Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_backup_config(server_id):
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
conf = {
|
||||
"backup_path": row.server_id.backup_path,
|
||||
"directories": row.directories,
|
||||
"max_backups": row.max_backups,
|
||||
"auto_enabled": row.schedule_id.enabled,
|
||||
"server_id": row.server_id.server_id
|
||||
}
|
||||
except IndexError:
|
||||
conf = {
|
||||
"backup_path": None,
|
||||
"directories": None,
|
||||
"max_backups": 0,
|
||||
"auto_enabled": True,
|
||||
"server_id": server_id
|
||||
}
|
||||
return conf
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
logger.debug("Updating server {} backup config with {}".format(server_id, locals()))
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
new_row = False
|
||||
conf = {}
|
||||
schd = {}
|
||||
except IndexError:
|
||||
conf = {
|
||||
"directories": None,
|
||||
"max_backups": 0,
|
||||
"server_id": server_id
|
||||
}
|
||||
schd = {
|
||||
"enabled": True,
|
||||
"action": "backup_server",
|
||||
"interval_type": "days",
|
||||
"interval": 1,
|
||||
"start_time": "00:00",
|
||||
"server_id": server_id,
|
||||
"comment": "Default backup job"
|
||||
}
|
||||
new_row = True
|
||||
if max_backups is not None:
|
||||
conf['max_backups'] = max_backups
|
||||
schd['enabled'] = bool(auto_enabled)
|
||||
if not new_row:
|
||||
with database.atomic():
|
||||
if backup_path is not None:
|
||||
u1 = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id).execute()
|
||||
else:
|
||||
u1 = 0
|
||||
u2 = Backups.update(conf).where(Backups.server_id == server_id).execute()
|
||||
u3 = Schedules.update(schd).where(Schedules.schedule_id == row.schedule_id).execute()
|
||||
logger.debug("Updating existing backup record. {}+{}+{} rows affected".format(u1, u2, u3))
|
||||
else:
|
||||
with database.atomic():
|
||||
conf["server_id"] = server_id
|
||||
if backup_path is not None:
|
||||
u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
|
||||
s = Schedules.create(**schd)
|
||||
conf['schedule_id'] = s.schedule_id
|
||||
b = Backups.create(**conf)
|
||||
logger.debug("Creating new backup record.")
|
||||
|
||||
|
||||
management_helper = helpers_management()
|
85
app/classes/models/roles.py
Normal file
@ -0,0 +1,85 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
#************************************************************************************************
|
||||
# Roles Class
|
||||
#************************************************************************************************
|
||||
class Roles(Model):
|
||||
role_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
last_update = DateTimeField(default=datetime.datetime.now)
|
||||
role_name = CharField(default="", unique=True, index=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "roles"
|
||||
database = database
|
||||
|
||||
#************************************************************************************************
|
||||
# Roles Helpers
|
||||
#************************************************************************************************
|
||||
class helper_roles:
|
||||
@staticmethod
|
||||
def get_all_roles():
|
||||
query = Roles.select()
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
def get_roleid_by_name(role_name):
|
||||
try:
|
||||
return (Roles.get(Roles.role_name == role_name)).role_id
|
||||
except DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_role(role_id):
|
||||
return model_to_dict(Roles.get(Roles.role_id == role_id))
|
||||
|
||||
@staticmethod
|
||||
def add_role(role_name):
|
||||
role_id = Roles.insert({
|
||||
Roles.role_name: role_name.lower(),
|
||||
Roles.created: helper.get_time_as_string()
|
||||
}).execute()
|
||||
return role_id
|
||||
|
||||
@staticmethod
|
||||
def update_role(role_id, up_data):
|
||||
return Roles.update(up_data).where(Roles.role_id == role_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def remove_role(role_id):
|
||||
with database.atomic():
|
||||
role = Roles.get(Roles.role_id == role_id)
|
||||
return role.delete_instance()
|
||||
|
||||
@staticmethod
|
||||
def role_id_exists(role_id):
|
||||
if not roles_helper.get_role(role_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
roles_helper = helper_roles()
|
164
app/classes/models/server_permissions.py
Normal file
@ -0,0 +1,164 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.roles import Roles
|
||||
from app.classes.models.users import users_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Role Servers Class
|
||||
#************************************************************************************************
|
||||
class Role_Servers(Model):
|
||||
role_id = ForeignKeyField(Roles, backref='role_server')
|
||||
server_id = ForeignKeyField(Servers, backref='role_server')
|
||||
permissions = CharField(default="00000000")
|
||||
|
||||
class Meta:
|
||||
table_name = 'role_servers'
|
||||
primary_key = CompositeKey('role_id', 'server_id')
|
||||
database = database
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Permissions Class
|
||||
#************************************************************************************************
|
||||
class Enum_Permissions_Server(Enum):
|
||||
Commands = 0
|
||||
Terminal = 1
|
||||
Logs = 2
|
||||
Schedule = 3
|
||||
Backup = 4
|
||||
Files = 5
|
||||
Config = 6
|
||||
Players = 7
|
||||
|
||||
class Permissions_Servers:
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
|
||||
@staticmethod
|
||||
def get_permissions_list():
|
||||
permissions_list = []
|
||||
for member in Enum_Permissions_Server.__members__.items():
|
||||
permissions_list.append(member[1])
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def get_permissions(permissions_mask):
|
||||
permissions_list = []
|
||||
for member in Enum_Permissions_Server.__members__.items():
|
||||
if server_permissions.has_permission(permissions_mask, member[1]):
|
||||
permissions_list.append(member[1])
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def has_permission(permission_mask, permission_tested: Enum_Permissions_Server):
|
||||
result = False
|
||||
if permission_mask[permission_tested.value] == '1':
|
||||
result = True
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value):
|
||||
list_perms = list(permission_mask)
|
||||
list_perms[permission_tested.value] = str(value)
|
||||
permission_mask = ''.join(list_perms)
|
||||
return permission_mask
|
||||
|
||||
@staticmethod
|
||||
def get_permission(permission_mask, permission_tested: Enum_Permissions_Server):
|
||||
return permission_mask[permission_tested.value]
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Role_Servers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_role_servers_from_role_id(roleid):
|
||||
return Role_Servers.select().where(Role_Servers.role_id == roleid)
|
||||
|
||||
@staticmethod
|
||||
def get_servers_from_role(role_id):
|
||||
return Role_Servers.select().join(Servers, JOIN.INNER).where(Role_Servers.role_id == role_id)
|
||||
|
||||
@staticmethod
|
||||
def get_roles_from_server(server_id):
|
||||
return Role_Servers.select().join(Roles, JOIN.INNER).where(Role_Servers.server_id == server_id)
|
||||
|
||||
@staticmethod
|
||||
def add_role_server(server_id, role_id, rs_permissions="00000000"):
|
||||
servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id, Role_Servers.permissions: rs_permissions}).execute()
|
||||
return servers
|
||||
|
||||
@staticmethod
|
||||
def get_permissions_mask(role_id, server_id):
|
||||
permissions_mask = ''
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).execute()
|
||||
permissions_mask = role_server.permissions
|
||||
return permissions_mask
|
||||
|
||||
@staticmethod
|
||||
def get_role_permissions_list(role_id):
|
||||
permissions_mask = '00000000'
|
||||
role_server = Role_Servers.get_or_none(role_id)
|
||||
if role_server is not None:
|
||||
permissions_mask = role_server.permissions
|
||||
permissions_list = server_permissions.get_permissions(permissions_mask)
|
||||
return permissions_list
|
||||
|
||||
@staticmethod
|
||||
def update_role_permission(role_id, server_id, permissions_mask):
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).get()
|
||||
role_server.permissions = permissions_mask
|
||||
Role_Servers.save(role_server)
|
||||
|
||||
@staticmethod
|
||||
def delete_roles_permissions(role_id, removed_servers={}):
|
||||
return Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute()
|
||||
|
||||
@staticmethod
|
||||
def remove_roles_of_server(server_id):
|
||||
with database.atomic():
|
||||
return Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_user_permissions_list(user_id, server_id):
|
||||
permissions_mask = ''
|
||||
permissions_list = []
|
||||
|
||||
user = users_helper.get_user(user_id)
|
||||
if user['superuser'] == True:
|
||||
permissions_list = server_permissions.get_permissions_list()
|
||||
else:
|
||||
roles_list = users_helper.get_user_roles_id(user_id)
|
||||
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == int(server_id)).execute()
|
||||
permissions_mask = role_server[0].permissions
|
||||
permissions_list = server_permissions.get_permissions(permissions_mask)
|
||||
return permissions_list
|
||||
|
||||
server_permissions = Permissions_Servers()
|
205
app/classes/models/servers.py
Normal file
@ -0,0 +1,205 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.main_models import db_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Class
|
||||
#************************************************************************************************
|
||||
class Servers(Model):
|
||||
server_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_uuid = CharField(default="", index=True)
|
||||
server_name = CharField(default="Server", index=True)
|
||||
path = CharField(default="")
|
||||
backup_path = CharField(default="")
|
||||
executable = CharField(default="")
|
||||
log_path = CharField(default="")
|
||||
execution_command = CharField(default="")
|
||||
auto_start = BooleanField(default=0)
|
||||
auto_start_delay = IntegerField(default=10)
|
||||
crash_detection = BooleanField(default=0)
|
||||
stop_command = CharField(default="stop")
|
||||
executable_update_url = CharField(default="")
|
||||
server_ip = CharField(default="127.0.0.1")
|
||||
server_port = IntegerField(default=25565)
|
||||
logs_delete_after = IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
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)
|
||||
|
||||
|
||||
class Meta:
|
||||
table_name = "server_stats"
|
||||
database = database
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Class
|
||||
#************************************************************************************************
|
||||
class helper_servers:
|
||||
|
||||
#************************************************************************************************
|
||||
# Generic Servers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
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.stop_command: server_stop,
|
||||
Servers.backup_path: backup_path
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def remove_server(server_id):
|
||||
with database.atomic():
|
||||
Servers.delete().where(Servers.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_server_data_by_id(server_id):
|
||||
query = Servers.select().where(Servers.server_id == server_id).limit(1)
|
||||
try:
|
||||
return db_helper.return_rows(query)[0]
|
||||
except IndexError:
|
||||
return {}
|
||||
|
||||
#************************************************************************************************
|
||||
# Servers Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def get_all_defined_servers():
|
||||
query = Servers.select()
|
||||
return db_helper.return_rows(query)
|
||||
|
||||
@staticmethod
|
||||
def get_all_servers_stats():
|
||||
servers = servers_helper.get_all_defined_servers()
|
||||
server_data = []
|
||||
|
||||
for s in servers:
|
||||
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":True})
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_server_friendly_name(server_id):
|
||||
server_data = servers_helper.get_server_data_by_id(server_id)
|
||||
friendly_name = "{} with ID: {}".format(server_data.get('server_name', None), server_data.get('server_id', 0))
|
||||
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 set_update(server_id, value):
|
||||
try:
|
||||
row = Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
except Exception as ex:
|
||||
logger.error("Database entry not found. ".format(ex))
|
||||
with database.atomic():
|
||||
Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute()
|
||||
|
||||
@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:
|
||||
row = Server_Stats.select().where(Server_Stats.server_id == server_id)
|
||||
except Exception as ex:
|
||||
logger.error("Database entry not found. ".format(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()
|
247
app/classes/models/users.py
Normal file
@ -0,0 +1,247 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.models.roles import Roles, roles_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
#************************************************************************************************
|
||||
# Users Class
|
||||
#************************************************************************************************
|
||||
class Users(Model):
|
||||
user_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
last_login = DateTimeField(default=datetime.datetime.now)
|
||||
last_update = DateTimeField(default=datetime.datetime.now)
|
||||
last_ip = CharField(default="")
|
||||
username = CharField(default="", unique=True, index=True)
|
||||
password = CharField(default="")
|
||||
enabled = BooleanField(default=True)
|
||||
superuser = BooleanField(default=False)
|
||||
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
|
||||
lang = CharField(default="en_EN")
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
database = database
|
||||
|
||||
#************************************************************************************************
|
||||
# User Roles Class
|
||||
#************************************************************************************************
|
||||
class User_Roles(Model):
|
||||
user_id = ForeignKeyField(Users, backref='user_role')
|
||||
role_id = ForeignKeyField(Roles, backref='user_role')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_roles'
|
||||
primary_key = CompositeKey('user_id', 'role_id')
|
||||
database = database
|
||||
|
||||
#************************************************************************************************
|
||||
# Users Helpers
|
||||
#************************************************************************************************
|
||||
class helper_users:
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(user_id):
|
||||
return Users.get_by_id(user_id)
|
||||
|
||||
@staticmethod
|
||||
def get_all_users():
|
||||
query = Users.select()
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
def get_user_lang_by_id(user_id):
|
||||
return Users.get(Users.user_id == user_id).lang
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_by_name(username):
|
||||
if username == "SYSTEM":
|
||||
return 0
|
||||
try:
|
||||
return (Users.get(Users.username == username)).user_id
|
||||
except DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_api_token(token: str):
|
||||
query = Users.select().where(Users.api_token == token)
|
||||
|
||||
if query.exists():
|
||||
user = model_to_dict(Users.get(Users.api_token == token))
|
||||
# I know it should apply it without setting it but I'm just making sure
|
||||
user = users_helper.add_user_roles(user)
|
||||
return user
|
||||
else:
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def user_query(user_id):
|
||||
user_query = Users.select().where(Users.user_id == user_id)
|
||||
return user_query
|
||||
|
||||
@staticmethod
|
||||
def get_user(user_id):
|
||||
if user_id == 0:
|
||||
return {
|
||||
'user_id': 0,
|
||||
'created': None,
|
||||
'last_login': None,
|
||||
'last_update': None,
|
||||
'last_ip': "127.27.23.89",
|
||||
'username': "SYSTEM",
|
||||
'password': None,
|
||||
'enabled': True,
|
||||
'superuser': False,
|
||||
'api_token': None,
|
||||
'roles': [],
|
||||
'servers': [],
|
||||
}
|
||||
user = model_to_dict(Users.get(Users.user_id == user_id))
|
||||
|
||||
if user:
|
||||
# I know it should apply it without setting it but I'm just making sure
|
||||
user = users_helper.add_user_roles(user)
|
||||
return user
|
||||
else:
|
||||
#logger.debug("user: ({}) {}".format(user_id, {}))
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
|
||||
if password is not None:
|
||||
pw_enc = helper.encode_pass(password)
|
||||
else:
|
||||
pw_enc = None
|
||||
if api_token is None:
|
||||
api_token = users_helper.new_api_token()
|
||||
else:
|
||||
if type(api_token) is not str and len(api_token) != 32:
|
||||
raise ValueError("API token must be a 32 character string")
|
||||
user_id = Users.insert({
|
||||
Users.username: username.lower(),
|
||||
Users.password: pw_enc,
|
||||
Users.api_token: api_token,
|
||||
Users.enabled: enabled,
|
||||
Users.superuser: superuser,
|
||||
Users.created: helper.get_time_as_string()
|
||||
}).execute()
|
||||
return user_id
|
||||
|
||||
@staticmethod
|
||||
def update_user(user_id, up_data={}):
|
||||
if up_data:
|
||||
Users.update(up_data).where(Users.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def remove_user(user_id):
|
||||
with database.atomic():
|
||||
User_Roles.delete().where(User_Roles.user_id == user_id).execute()
|
||||
user = Users.get(Users.user_id == user_id)
|
||||
return user.delete_instance()
|
||||
|
||||
@staticmethod
|
||||
def user_id_exists(user_id):
|
||||
if not users_helper.get_user(user_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def new_api_token():
|
||||
while True:
|
||||
token = helper.random_string_generator(32)
|
||||
test = list(Users.select(Users.user_id).where(Users.api_token == token))
|
||||
if len(test) == 0:
|
||||
return token
|
||||
|
||||
#************************************************************************************************
|
||||
# User_Roles Methods
|
||||
#************************************************************************************************
|
||||
|
||||
@staticmethod
|
||||
def get_or_create(user_id, role_id):
|
||||
return User_Roles.get_or_create(user_id=user_id, role_id=role_id)
|
||||
|
||||
@staticmethod
|
||||
def get_user_roles_id(user_id):
|
||||
roles_list = []
|
||||
roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
for r in roles:
|
||||
roles_list.append(roles_helper.get_role(r.role_id)['role_id'])
|
||||
return roles_list
|
||||
|
||||
@staticmethod
|
||||
def get_user_roles_names(user_id):
|
||||
roles_list = []
|
||||
roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
for r in roles:
|
||||
roles_list.append(roles_helper.get_role(r.role_id)['role_name'])
|
||||
return roles_list
|
||||
|
||||
@staticmethod
|
||||
def add_role_to_user(user_id, role_id):
|
||||
User_Roles.insert({
|
||||
User_Roles.user_id: user_id,
|
||||
User_Roles.role_id: role_id
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def add_user_roles(user):
|
||||
if type(user) == dict:
|
||||
user_id = user['user_id']
|
||||
else:
|
||||
user_id = user.user_id
|
||||
|
||||
# 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)
|
||||
# TODO: this query needs to be narrower
|
||||
roles = set()
|
||||
for r in roles_query:
|
||||
roles.add(r.role_id.role_id)
|
||||
|
||||
user['roles'] = roles
|
||||
#logger.debug("user: ({}) {}".format(user_id, user))
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def user_role_query(user_id):
|
||||
user_query = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
query = Roles.select().where(Roles.role_id == -1)
|
||||
for u in user_query:
|
||||
query = query + Roles.select().where(Roles.role_id == u.role_id)
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
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()
|
||||
|
||||
@staticmethod
|
||||
def remove_roles_from_role_id(role_id):
|
||||
User_Roles.delete().where(User_Roles.role_id == role_id).execute()
|
||||
|
||||
|
||||
users_helper = helper_users()
|
@ -2,7 +2,7 @@ import os
|
||||
import sys
|
||||
import cmd
|
||||
import time
|
||||
|
||||
import threading
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -16,23 +16,20 @@ try:
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class MainPrompt(cmd.Cmd, object):
|
||||
|
||||
def __init__(self, tasks_manager):
|
||||
def __init__(self, tasks_manager, migration_manager):
|
||||
super().__init__()
|
||||
self.tasks_manager = tasks_manager
|
||||
self.migration_manager = migration_manager
|
||||
|
||||
# overrides the default Prompt
|
||||
prompt = "Crafty Controller v{} > ".format(helper.get_version_string())
|
||||
|
||||
def __init__(self, tasks_manager):
|
||||
super().__init__()
|
||||
self.tasks_manager = tasks_manager
|
||||
|
||||
@staticmethod
|
||||
def emptyline():
|
||||
pass
|
||||
@ -49,6 +46,34 @@ class MainPrompt(cmd.Cmd, object):
|
||||
console.critical("Unable to write exit file due to error: {}".format(e))
|
||||
|
||||
def do_exit(self, line):
|
||||
self.universal_exit()
|
||||
|
||||
def do_migrations(self, line):
|
||||
if (line == 'up'):
|
||||
self.migration_manager.up()
|
||||
elif (line == 'down'):
|
||||
self.migration_manager.down()
|
||||
elif (line == 'done'):
|
||||
console.info(self.migration_manager.done)
|
||||
elif (line == 'todo'):
|
||||
console.info(self.migration_manager.todo)
|
||||
elif (line == 'diff'):
|
||||
console.info(self.migration_manager.diff)
|
||||
elif (line == 'info'):
|
||||
console.info('Done: {}'.format(self.migration_manager.done))
|
||||
console.info('FS: {}'.format(self.migration_manager.todo))
|
||||
console.info('Todo: {}'.format(self.migration_manager.diff))
|
||||
elif (line.startswith('add ')):
|
||||
migration_name = line[len('add '):]
|
||||
self.migration_manager.create(migration_name, False)
|
||||
else:
|
||||
console.info('Unknown migration command')
|
||||
|
||||
def do_threads(self, line):
|
||||
for thread in threading.enumerate():
|
||||
print(f'Name: {thread.name} IDENT: {thread.ident}')
|
||||
|
||||
def universal_exit(self):
|
||||
logger.info("Stopping all server daemons / threads")
|
||||
console.info("Stopping all server daemons / threads - This may take a few seconds")
|
||||
websocket_helper.disconnect_all()
|
||||
@ -63,3 +88,7 @@ class MainPrompt(cmd.Cmd, object):
|
||||
@staticmethod
|
||||
def help_exit():
|
||||
console.help("Stops the server if running, Exits the program")
|
||||
|
||||
@staticmethod
|
||||
def help_migrations():
|
||||
console.help("Only for advanced users. Use with caution")
|
||||
|
@ -9,8 +9,8 @@ try:
|
||||
from termcolor import colored
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logging.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
print("Import Error: Unable to load {} module".format(e, e.name))
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
print("Import Error: Unable to load {} module".format(e.name))
|
||||
from app.classes.shared.installer import installer
|
||||
installer.do_install()
|
||||
sys.exit(1)
|
||||
|
@ -2,6 +2,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
import string
|
||||
@ -12,11 +13,12 @@ import logging
|
||||
import html
|
||||
import zipfile
|
||||
import pathlib
|
||||
import shutil
|
||||
from requests import get
|
||||
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
|
||||
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -28,10 +30,15 @@ try:
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
class Helpers:
|
||||
allowed_quotes = [
|
||||
"\"",
|
||||
"'",
|
||||
"`"
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.root_dir = os.path.abspath(os.path.curdir)
|
||||
@ -39,6 +46,7 @@ class Helpers:
|
||||
self.webroot = os.path.join(self.root_dir, 'app', 'frontend')
|
||||
self.servers_dir = os.path.join(self.root_dir, 'servers')
|
||||
self.backup_path = os.path.join(self.root_dir, 'backups')
|
||||
self.migration_dir = os.path.join(self.root_dir, 'app', 'migrations')
|
||||
|
||||
self.session_file = os.path.join(self.root_dir, 'app', 'config', 'session.lock')
|
||||
self.settings_file = os.path.join(self.root_dir, 'app', 'config', 'config.json')
|
||||
@ -73,6 +81,63 @@ class Helpers:
|
||||
logger.error("{} does not exist".format(file))
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def check_internet():
|
||||
try:
|
||||
requests.get('https://google.com', timeout=1)
|
||||
return True
|
||||
except Exception as err:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_port(server_port):
|
||||
try:
|
||||
host_public = get('https://api.ipify.org').text
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10.0)
|
||||
result = sock.connect_ex((host_public ,server_port))
|
||||
sock.close()
|
||||
if result == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as err:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def cmdparse(cmd_in):
|
||||
# Parse a string into arguments
|
||||
cmd_out = [] # "argv" output array
|
||||
ci = -1 # command index - pointer to the argument we're building in cmd_out
|
||||
np = True # whether we're creating a new argument/parameter
|
||||
esc = False # whether an escape character was encountered
|
||||
stch = None # if we're dealing with a quote, save the quote type here. Nested quotes to be dealt with by the command
|
||||
for c in cmd_in: # for character in string
|
||||
if np == True: # if set, begin a new argument and increment the command index. Continue the loop.
|
||||
if c == ' ':
|
||||
continue
|
||||
else:
|
||||
ci += 1
|
||||
cmd_out.append("")
|
||||
np = False
|
||||
if esc: # if we encountered an escape character on the last loop, append this char regardless of what it is
|
||||
if c not in Helpers.allowed_quotes:
|
||||
cmd_out[ci] += '\\'
|
||||
cmd_out[ci] += c
|
||||
esc = False
|
||||
else:
|
||||
if c == '\\': # if the current character is an escape character, set the esc flag and continue to next loop
|
||||
esc = True
|
||||
elif c == ' ' and stch is None: # if we encounter a space and are not dealing with a quote, set the new argument flag and continue to next loop
|
||||
np = True
|
||||
elif c == stch: # if we encounter the character that matches our start quote, end the quote and continue to next loop
|
||||
stch = None
|
||||
elif stch is None and (c in Helpers.allowed_quotes): # if we're not in the middle of a quote and we get a quotable character, start a quote and proceed to the next loop
|
||||
stch = c
|
||||
else: # else, just store the character in the current arg
|
||||
cmd_out[ci] += c
|
||||
return cmd_out
|
||||
|
||||
def check_for_old_logs(self, db_helper):
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
for server in servers:
|
||||
@ -144,8 +209,8 @@ class Helpers:
|
||||
if r.status_code in [200, 201]:
|
||||
try:
|
||||
data = json.loads(r.content)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error("Failed to load json content with error: {} ".format(e))
|
||||
|
||||
return data
|
||||
|
||||
@ -157,7 +222,7 @@ class Helpers:
|
||||
version = "{}.{}.{}-{}".format(version_data.get('major', '?'),
|
||||
version_data.get('minor', '?'),
|
||||
version_data.get('sub', '?'),
|
||||
version_data.get('patch', '?'))
|
||||
version_data.get('meta', '?'))
|
||||
return str(version)
|
||||
|
||||
def do_exit(self):
|
||||
@ -193,6 +258,9 @@ class Helpers:
|
||||
(r'(\[.+?/ERROR\])', r'<span class="mc-log-error">\1</span>'),
|
||||
(r'(\w+?\[/\d+?\.\d+?\.\d+?\.\d+?\:\d+?\])', r'<span class="mc-log-keyword">\1</span>'),
|
||||
(r'\[(\d\d:\d\d:\d\d)\]', r'<span class="mc-log-time">[\1]</span>'),
|
||||
(r'(\[.+? INFO\])', r'<span class="mc-log-info">\1</span>'),
|
||||
(r'(\[.+? WARN\])', r'<span class="mc-log-warn">\1</span>'),
|
||||
(r'(\[.+? ERROR\])', r'<span class="mc-log-error">\1</span>')
|
||||
]
|
||||
|
||||
# highlight users keywords
|
||||
@ -253,6 +321,45 @@ class Helpers:
|
||||
logger.critical("Unable to write to {} - Error: {}".format(path, e))
|
||||
return False
|
||||
|
||||
def unzipFile(self, zip_path):
|
||||
new_dir_list = zip_path.split('/')
|
||||
new_dir = ''
|
||||
for i in range(len(new_dir_list)-1):
|
||||
if i == 0:
|
||||
new_dir += new_dir_list[i]
|
||||
else:
|
||||
new_dir += '/'+new_dir_list[i]
|
||||
|
||||
if helper.check_file_perms(zip_path) and os.path.isfile(zip_path):
|
||||
helper.ensure_dir_exists(new_dir)
|
||||
tempDir = tempfile.mkdtemp()
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(tempDir)
|
||||
for i in range(len(zip_ref.filelist)):
|
||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
|
||||
test = zip_ref.filelist[i].filename
|
||||
break
|
||||
path_list = test.split('/')
|
||||
root_path = path_list[0]
|
||||
'''
|
||||
if len(path_list) > 1:
|
||||
for i in range(len(path_list) - 2):
|
||||
root_path = os.path.join(root_path, path_list[i + 1])
|
||||
'''
|
||||
full_root_path = tempDir
|
||||
|
||||
for item in os.listdir(full_root_path):
|
||||
try:
|
||||
shutil.move(os.path.join(full_root_path, item), os.path.join(new_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
else:
|
||||
return "false"
|
||||
return
|
||||
|
||||
def ensure_logging_setup(self):
|
||||
log_file = os.path.join(os.path.curdir, 'logs', 'commander.log')
|
||||
session_log_file = os.path.join(os.path.curdir, 'logs', 'session.log')
|
||||
@ -270,7 +377,7 @@ class Helpers:
|
||||
try:
|
||||
os.makedirs(os.path.join(self.root_dir, 'logs'))
|
||||
except Exception as e:
|
||||
pass
|
||||
console.error("Failed to make logs directory with error: {} ".format(e))
|
||||
|
||||
# ensure the log file is there
|
||||
try:
|
||||
@ -282,8 +389,8 @@ class Helpers:
|
||||
# del any old session.lock file as this is a new session
|
||||
try:
|
||||
os.remove(session_log_file)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error("Deleting Session.lock failed with error: {} ".format(e))
|
||||
|
||||
@staticmethod
|
||||
def get_time_as_string():
|
||||
@ -354,7 +461,9 @@ class Helpers:
|
||||
started = data.get('started')
|
||||
console.critical("Another Crafty Controller agent seems to be running...\npid: {} \nstarted on: {}".format(pid, started))
|
||||
except Exception as e:
|
||||
pass
|
||||
logger.error("Failed to locate existing session.lock with error: {} ".format(e))
|
||||
console.error("Failed to locate existing session.lock with error: {} ".format(e))
|
||||
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
@ -386,7 +495,7 @@ class Helpers:
|
||||
sizes = []
|
||||
for p in paths:
|
||||
sizes.append({
|
||||
"path": p,
|
||||
"path": p,
|
||||
"size": self.human_readable_file_size(os.stat(p).st_size)
|
||||
})
|
||||
return sizes
|
||||
@ -534,32 +643,49 @@ class Helpers:
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def in_path(x, y):
|
||||
return os.path.abspath(y).__contains__(os.path.abspath(x))
|
||||
|
||||
def in_path(parent_path, child_path):
|
||||
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
|
||||
parent_path = os.path.abspath(parent_path)
|
||||
child_path = os.path.abspath(child_path)
|
||||
|
||||
# Compare the common path of the parent and child path with the common path of just the parent path. Using the commonpath method on just the parent path will regularise the path name in the same way as the comparison that deals with both paths, removing any trailing path separator
|
||||
return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])
|
||||
|
||||
@staticmethod
|
||||
def get_banned_players(server_id, db_helper):
|
||||
stats = db_helper.get_server_stats_by_id(server_id)
|
||||
server_path = stats['server_id']['path']
|
||||
path = os.path.join(server_path, 'banned-players.json')
|
||||
def in_path_old(x, y):
|
||||
return os.path.abspath(y).__contains__(os.path.abspath(x))
|
||||
|
||||
@staticmethod
|
||||
def copy_files(source, dest):
|
||||
if os.path.isfile(source):
|
||||
shutil.copyfile(source, dest)
|
||||
logger.info("Copying jar %s to %s", source, dest)
|
||||
else:
|
||||
logger.info("Source jar does not exist.")
|
||||
|
||||
@staticmethod
|
||||
def download_file(executable_url, jar_path):
|
||||
try:
|
||||
r = requests.get(executable_url, timeout=5)
|
||||
except Exception as ex:
|
||||
logger.error("Could not download executable: %s", ex)
|
||||
return False
|
||||
if r.status_code != 200:
|
||||
logger.error("Unable to download file from %s", executable_url)
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(path) as file:
|
||||
content = file.read()
|
||||
file.close()
|
||||
except Exception as ex:
|
||||
print (ex)
|
||||
return None
|
||||
|
||||
return json.loads(content)
|
||||
open(jar_path, "wb").write(r.content)
|
||||
except Exception as e:
|
||||
logger.error("Unable to finish executable download. Error: %s", e)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@staticmethod
|
||||
def zip_directory(file, path, compression=zipfile.ZIP_LZMA):
|
||||
with zipfile.ZipFile(file, 'w', compression) as zf:
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
zf.write(os.path.join(root, file),
|
||||
os.path.relpath(os.path.join(root, file),
|
||||
os.path.join(path, '..')))
|
||||
def remove_prefix(text, prefix):
|
||||
if text.startswith(prefix):
|
||||
return text[len(prefix):]
|
||||
return text
|
||||
|
||||
helper = Helpers()
|
||||
|
@ -4,13 +4,24 @@ import logging
|
||||
import sys
|
||||
import yaml
|
||||
import asyncio
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from distutils import dir_util
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.models import db_helper
|
||||
#Importing Models
|
||||
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
|
||||
from app.classes.models.servers import servers_helper
|
||||
#Importing Controllers
|
||||
from app.classes.controllers.crafty_perms_controller import Crafty_Perms_Controller
|
||||
from app.classes.controllers.management_controller import Management_Controller
|
||||
from app.classes.controllers.users_controller import Users_Controller
|
||||
from app.classes.controllers.roles_controller import Roles_Controller
|
||||
from app.classes.controllers.server_perms_controller import Server_Perms_Controller
|
||||
from app.classes.controllers.servers_controller import Servers_Controller
|
||||
|
||||
from app.classes.shared.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
@ -19,12 +30,17 @@ from app.classes.minecraft.stats import Stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Controller:
|
||||
|
||||
def __init__(self):
|
||||
self.servers_list = []
|
||||
self.stats = Stats(self)
|
||||
self.crafty_perms = Crafty_Perms_Controller()
|
||||
self.management = Management_Controller()
|
||||
self.roles = Roles_Controller()
|
||||
self.server_perms = Server_Perms_Controller()
|
||||
self.servers = Servers_Controller()
|
||||
self.users = Users_Controller()
|
||||
|
||||
def check_server_loaded(self, server_id_to_check: int):
|
||||
|
||||
@ -43,7 +59,7 @@ class Controller:
|
||||
|
||||
def init_all_servers(self):
|
||||
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
servers = self.servers.get_all_defined_servers()
|
||||
|
||||
for s in servers:
|
||||
server_id = s.get('server_id')
|
||||
@ -84,6 +100,11 @@ class Controller:
|
||||
# add this temp object to the list of init servers
|
||||
self.servers_list.append(temp_server_dict)
|
||||
|
||||
if s['auto_start']:
|
||||
self.servers.set_waiting_start(s['server_id'], True)
|
||||
|
||||
self.refresh_server_settings(s['server_id'])
|
||||
|
||||
console.info("Loaded Server: ID {} | Name: {} | Autostart: {} | Delay: {} ".format(
|
||||
s['server_id'],
|
||||
s['server_name'],
|
||||
@ -95,6 +116,14 @@ class Controller:
|
||||
server_obj = self.get_server_obj(server_id)
|
||||
server_obj.reload_server_settings()
|
||||
|
||||
def get_server_settings(self, server_id):
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
return s['server_settings']
|
||||
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
return False
|
||||
|
||||
def get_server_obj(self, server_id):
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
@ -103,18 +132,6 @@ class Controller:
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def list_defined_servers():
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
return servers
|
||||
|
||||
@staticmethod
|
||||
def list_authorized_servers(userId):
|
||||
#servers = db_helper.get_authorized_servers(userId)
|
||||
servers = db_helper.get_authorized_servers_from_roles(userId)
|
||||
logger.debug("servers list = {}".format(servers))
|
||||
return servers
|
||||
|
||||
def get_server_data(self, server_id):
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
@ -123,6 +140,11 @@ class Controller:
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def list_defined_servers():
|
||||
servers = servers_helper.get_all_defined_servers()
|
||||
return servers
|
||||
|
||||
def list_running_servers(self):
|
||||
running_servers = []
|
||||
|
||||
@ -162,38 +184,9 @@ class Controller:
|
||||
console.info("All Servers Stopped")
|
||||
|
||||
def stop_server(self, server_id):
|
||||
# get object
|
||||
svr_obj = self.get_server_obj(server_id)
|
||||
svr_data = self.get_server_data(server_id)
|
||||
server_name = svr_data['server_name']
|
||||
|
||||
running = svr_obj.check_running()
|
||||
|
||||
# issue the stop command
|
||||
svr_obj.stop_threaded_server()
|
||||
|
||||
# while it's running, we wait
|
||||
x = 0
|
||||
while running:
|
||||
logger.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
console.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
running = svr_obj.check_running()
|
||||
|
||||
# let's keep track of how long this is going on...
|
||||
x = x + 1
|
||||
|
||||
# if we have been waiting more than 120 seconds. let's just kill the pid
|
||||
if x >= 60:
|
||||
logger.error("Server {} is taking way too long to stop. Killing this process".format(server_name))
|
||||
console.error("Server {} is taking way too long to stop. Killing this process".format(server_name))
|
||||
|
||||
svr_obj.killpid(svr_obj.PID)
|
||||
running = False
|
||||
|
||||
# if we killed the server, let's clean up the object
|
||||
if not running:
|
||||
svr_obj.cleanup_server_object()
|
||||
|
||||
def create_jar_server(self, server: str, version: str, name: str, min_mem: int, max_mem: int, port: int):
|
||||
server_id = helper.create_uuid()
|
||||
server_dir = os.path.join(helper.servers_dir, server_id)
|
||||
@ -209,7 +202,7 @@ class Controller:
|
||||
try:
|
||||
# do a eula.txt
|
||||
with open(os.path.join(server_dir, "eula.txt"), 'w') as f:
|
||||
f.write("eula=true")
|
||||
f.write("eula=false")
|
||||
f.close()
|
||||
|
||||
# setup server.properties with the port
|
||||
@ -228,7 +221,7 @@ class Controller:
|
||||
server_stop = "stop"
|
||||
|
||||
# download the jar
|
||||
server_jar_obj.download_jar(server, version, full_jar_path)
|
||||
server_jar_obj.download_jar(server, version, full_jar_path, name)
|
||||
|
||||
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop)
|
||||
return new_id
|
||||
@ -276,8 +269,35 @@ class Controller:
|
||||
if helper.check_file_perms(zip_path):
|
||||
helper.ensure_dir_exists(new_server_dir)
|
||||
helper.ensure_dir_exists(backup_path)
|
||||
tempDir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(new_server_dir)
|
||||
zip_ref.extractall(tempDir)
|
||||
for i in range(len(zip_ref.filelist)):
|
||||
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
|
||||
test = zip_ref.filelist[i].filename
|
||||
break
|
||||
path_list = test.split('/')
|
||||
root_path = path_list[0]
|
||||
if len(path_list) > 1:
|
||||
for i in range(len(path_list)-2):
|
||||
root_path = os.path.join(root_path, path_list[i+1])
|
||||
|
||||
full_root_path = os.path.join(tempDir, root_path)
|
||||
|
||||
has_properties = False
|
||||
for item in os.listdir(full_root_path):
|
||||
if str(item) == 'server.properties':
|
||||
has_properties = True
|
||||
try:
|
||||
shutil.move(os.path.join(full_root_path, item), os.path.join(new_server_dir, item))
|
||||
except Exception as ex:
|
||||
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
|
||||
if not has_properties:
|
||||
logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port)))
|
||||
with open(os.path.join(new_server_dir, "server.properties"), "w") as f:
|
||||
f.write("server-port={}".format(port))
|
||||
f.close()
|
||||
zip_ref.close()
|
||||
else:
|
||||
return "false"
|
||||
|
||||
@ -285,7 +305,7 @@ class Controller:
|
||||
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
|
||||
helper.float_to_string(max_mem),
|
||||
full_jar_path)
|
||||
print('command: ' + server_command)
|
||||
logger.debug('command: ' + server_command)
|
||||
server_log_file = "{}/logs/latest.log".format(new_server_dir)
|
||||
server_stop = "stop"
|
||||
|
||||
@ -295,7 +315,7 @@ class Controller:
|
||||
|
||||
def register_server(self, name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
# put data in the db
|
||||
new_id = db_helper.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
|
||||
new_id = self.servers.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
|
||||
|
||||
try:
|
||||
# place a file in the dir saying it's owned by crafty
|
||||
@ -313,7 +333,7 @@ class Controller:
|
||||
|
||||
return new_id
|
||||
|
||||
def remove_server(self, server_id):
|
||||
def remove_server(self, server_id, files):
|
||||
counter = 0
|
||||
for s in self.servers_list:
|
||||
|
||||
@ -330,9 +350,10 @@ class Controller:
|
||||
|
||||
if running:
|
||||
self.stop_server(server_id)
|
||||
|
||||
if files:
|
||||
shutil.rmtree(self.servers.get_server_data_by_id(server_id)['path'])
|
||||
# remove the server from the DB
|
||||
db_helper.remove_server(server_id)
|
||||
self.servers.remove_server(server_id)
|
||||
|
||||
# remove the server from servers list
|
||||
self.servers_list.pop(counter)
|
94
app/classes/shared/main_models.py
Normal file
@ -0,0 +1,94 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.models.users import Users, users_helper
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from enum import Enum
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
class db_builder:
|
||||
|
||||
@staticmethod
|
||||
def default_settings():
|
||||
logger.info("Fresh Install Detected - Creating Default Settings")
|
||||
console.info("Fresh Install Detected - Creating Default Settings")
|
||||
default_data = helper.find_default_password()
|
||||
|
||||
username = default_data.get("username", 'admin')
|
||||
password = default_data.get("password", 'crafty')
|
||||
#api_token = helper.random_string_generator(32)
|
||||
#
|
||||
#Users.insert({
|
||||
# Users.username: username.lower(),
|
||||
# Users.password: helper.encode_pass(password),
|
||||
# Users.api_token: api_token,
|
||||
# Users.enabled: True,
|
||||
# Users.superuser: True
|
||||
#}).execute()
|
||||
user_id = users_helper.add_user(username=username, password=password, superuser=True)
|
||||
#users_helper.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} )
|
||||
|
||||
#console.info("API token is {}".format(api_token))
|
||||
|
||||
@staticmethod
|
||||
def is_fresh_install():
|
||||
try:
|
||||
user = users_helper.get_by_id(1)
|
||||
return False
|
||||
except:
|
||||
return True
|
||||
pass
|
||||
|
||||
class db_shortcuts:
|
||||
|
||||
#************************************************************************************************
|
||||
# Generic Databse Methods
|
||||
#************************************************************************************************
|
||||
@staticmethod
|
||||
def return_rows(query):
|
||||
rows = []
|
||||
|
||||
try:
|
||||
if query.count() > 0:
|
||||
for s in query:
|
||||
rows.append(model_to_dict(s))
|
||||
except Exception as e:
|
||||
logger.warning("Database Error: {}".format(e))
|
||||
pass
|
||||
|
||||
return rows
|
||||
|
||||
@staticmethod
|
||||
def return_db_rows(model):
|
||||
data = [model_to_dict(row) for row in model]
|
||||
return data
|
||||
|
||||
|
||||
#************************************************************************************************
|
||||
# Static Accessors
|
||||
#************************************************************************************************
|
||||
installer = db_builder()
|
||||
db_helper = db_shortcuts()
|
536
app/classes/shared/migration.py
Normal file
@ -0,0 +1,536 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import typing as t
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from importlib import import_module
|
||||
from functools import wraps
|
||||
|
||||
try:
|
||||
from functools import cached_property
|
||||
except ImportError:
|
||||
from cached_property import cached_property
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import peewee
|
||||
from playhouse.migrate import (
|
||||
SchemaMigrator as ScM,
|
||||
SqliteMigrator as SqM,
|
||||
Operation, SQL, operation, SqliteDatabase,
|
||||
make_index_name, Context
|
||||
)
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(
|
||||
e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class MigrateHistory(peewee.Model):
|
||||
"""
|
||||
Presents the migration history in a database.
|
||||
"""
|
||||
|
||||
name = peewee.CharField(unique=True)
|
||||
migrated_at = peewee.DateTimeField(default=datetime.utcnow)
|
||||
|
||||
def __unicode__(self) -> str:
|
||||
"""
|
||||
String representation of this migration
|
||||
"""
|
||||
return self.name
|
||||
|
||||
|
||||
MIGRATE_TABLE = 'migratehistory'
|
||||
MIGRATE_TEMPLATE = '''# Generated by database migrator
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
{migrate}
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
||||
{rollback}'''
|
||||
VOID: t.Callable = lambda m, d: None
|
||||
|
||||
|
||||
def get_model(method):
|
||||
"""
|
||||
Convert string to model class.
|
||||
"""
|
||||
|
||||
@wraps(method)
|
||||
def wrapper(migrator, model, *args, **kwargs):
|
||||
if isinstance(model, str):
|
||||
return method(migrator, migrator.orm[model], *args, **kwargs)
|
||||
return method(migrator, model, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class Migrator(object):
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migrator
|
||||
"""
|
||||
if isinstance(database, peewee.Proxy):
|
||||
database = database.obj
|
||||
self.database: SqliteDatabase = database
|
||||
self.orm: t.Dict[str, peewee.Model] = {}
|
||||
self.operations: t.List[Operation] = []
|
||||
self.migrator = SqliteMigrator(database)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs operations.
|
||||
"""
|
||||
for op in self.operations:
|
||||
if isinstance(op, Operation):
|
||||
op.run()
|
||||
else:
|
||||
op()
|
||||
self.clean()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Cleans the operations.
|
||||
"""
|
||||
self.operations = list()
|
||||
|
||||
def sql(self, sql: str, *params):
|
||||
"""
|
||||
Executes raw SQL.
|
||||
"""
|
||||
self.operations.append(self.migrator.sql(sql, *params))
|
||||
|
||||
def create_table(self, model: peewee.Model) -> peewee.Model:
|
||||
"""
|
||||
Creates model and table in database.
|
||||
"""
|
||||
self.orm[model._meta.table_name] = model
|
||||
model._meta.database = self.database
|
||||
self.operations.append(model.create_table)
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_table(self, model: peewee.Model):
|
||||
"""
|
||||
Drops model and table from database.
|
||||
"""
|
||||
del self.orm[model._meta.table_name]
|
||||
self.operations.append(self.migrator.drop_table(model))
|
||||
|
||||
@get_model
|
||||
def add_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
|
||||
"""
|
||||
Creates new fields.
|
||||
"""
|
||||
for name, field in fields.items():
|
||||
model._meta.add_field(name, field)
|
||||
self.operations.append(self.migrator.add_column(
|
||||
model._meta.table_name, field.column_name, field))
|
||||
if field.unique:
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, (field.column_name,), unique=True))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def change_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
|
||||
"""
|
||||
Changes fields.
|
||||
"""
|
||||
for name, field in fields.items():
|
||||
old_field = model._meta.fields.get(name, field)
|
||||
old_column_name = old_field and old_field.column_name
|
||||
|
||||
model._meta.add_field(name, field)
|
||||
|
||||
if isinstance(old_field, peewee.ForeignKeyField):
|
||||
self.operations.append(self.migrator.drop_foreign_key_constraint(
|
||||
model._meta.table_name, old_column_name))
|
||||
|
||||
if old_column_name != field.column_name:
|
||||
self.operations.append(
|
||||
self.migrator.rename_column(
|
||||
model._meta.table_name, old_column_name, field.column_name))
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
on_delete = field.on_delete if field.on_delete else 'RESTRICT'
|
||||
on_update = field.on_update if field.on_update else 'RESTRICT'
|
||||
self.operations.append(self.migrator.add_foreign_key_constraint(
|
||||
model._meta.table_name, field.column_name,
|
||||
field.rel_model._meta.table_name, field.rel_field.name,
|
||||
on_delete, on_update))
|
||||
continue
|
||||
|
||||
self.operations.append(self.migrator.change_column(
|
||||
model._meta.table_name, field.column_name, field))
|
||||
|
||||
if field.unique == old_field.unique:
|
||||
continue
|
||||
|
||||
if field.unique:
|
||||
index = (field.column_name,), field.unique
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, *index))
|
||||
model._meta.indexes.append(index)
|
||||
else:
|
||||
index = (field.column_name,), old_field.unique
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, *index))
|
||||
model._meta.indexes.remove(index)
|
||||
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_columns(self, model: peewee.Model, names: str, **kwargs) -> peewee.Model:
|
||||
"""
|
||||
Removes fields from model.
|
||||
"""
|
||||
fields = [field for field in model._meta.fields.values()
|
||||
if field.name in names]
|
||||
cascade = kwargs.pop('cascade', True)
|
||||
for field in fields:
|
||||
self.__del_field__(model, field)
|
||||
if field.unique:
|
||||
index_name = make_index_name(
|
||||
model._meta.table_name, [field.column_name])
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
self.operations.append(
|
||||
self.migrator.drop_column(
|
||||
model._meta.table_name, field.column_name, cascade=False))
|
||||
return model
|
||||
|
||||
def __del_field__(self, model: peewee.Model, field: peewee.Field):
|
||||
"""
|
||||
Deletes field from model.
|
||||
"""
|
||||
model._meta.remove_field(field.name)
|
||||
delattr(model, field.name)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
obj_id_name = field.column_name
|
||||
if field.column_name == field.name:
|
||||
obj_id_name += '_id'
|
||||
delattr(model, obj_id_name)
|
||||
delattr(field.rel_model, field.backref)
|
||||
|
||||
@get_model
|
||||
def rename_column(self, model: peewee.Model, old_name: str, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames field in model.
|
||||
"""
|
||||
field = model._meta.fields[old_name]
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
old_name = field.column_name
|
||||
self.__del_field__(model, field)
|
||||
field.name = field.column_name = new_name
|
||||
model._meta.add_field(new_name, field)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
field.column_name = new_name = field.column_name + '_id'
|
||||
self.operations.append(self.migrator.rename_column(
|
||||
model._meta.table_name, old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def rename_table(self, model: peewee.Model, new_name: str) -> peewee.Model:
|
||||
"""
|
||||
Renames table in database.
|
||||
"""
|
||||
old_name = model._meta.table_name
|
||||
del self.orm[model._meta.table_name]
|
||||
model._meta.table_name = new_name
|
||||
self.orm[model._meta.table_name] = model
|
||||
self.operations.append(self.migrator.rename_table(old_name, new_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_index(self, model: peewee.Model, *columns: str, **kwargs) -> peewee.Model:
|
||||
"""Create indexes."""
|
||||
unique = kwargs.pop('unique', False)
|
||||
model._meta.indexes.append((columns, unique))
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = unique
|
||||
field.index = not unique
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
|
||||
columns_.append(col)
|
||||
self.operations.append(self.migrator.add_index(
|
||||
model._meta.table_name, columns_, unique=unique))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_index(self, model: peewee.Model, *columns: str) -> peewee.Model:
|
||||
"""Drop indexes."""
|
||||
columns_ = []
|
||||
for col in columns:
|
||||
field = model._meta.fields.get(col)
|
||||
if not field:
|
||||
continue
|
||||
|
||||
if len(columns) == 1:
|
||||
field.unique = field.index = False
|
||||
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
col = col + '_id'
|
||||
columns_.append(col)
|
||||
index_name = make_index_name(model._meta.table_name, columns_)
|
||||
model._meta.indexes = [(cols, _) for (
|
||||
cols, _) in model._meta.indexes if columns != cols]
|
||||
self.operations.append(self.migrator.drop_index(
|
||||
model._meta.table_name, index_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Add not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = False
|
||||
self.operations.append(self.migrator.add_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def drop_not_null(self, model: peewee.Model, *names: str) -> peewee.Model:
|
||||
"""Drop not null."""
|
||||
for name in names:
|
||||
field = model._meta.fields[name]
|
||||
field.null = True
|
||||
self.operations.append(self.migrator.drop_not_null(
|
||||
model._meta.table_name, field.column_name))
|
||||
return model
|
||||
|
||||
@get_model
|
||||
def add_default(self, model: peewee.Model, name: str, default: t.Any) -> peewee.Model:
|
||||
"""Add default."""
|
||||
field = model._meta.fields[name]
|
||||
model._meta.defaults[field] = field.default = default
|
||||
self.operations.append(self.migrator.apply_default(
|
||||
model._meta.table_name, name, field))
|
||||
return model
|
||||
|
||||
|
||||
class SqliteMigrator(SqM):
|
||||
def drop_table(self, model):
|
||||
return lambda: model.drop_table(cascade=False)
|
||||
|
||||
@operation
|
||||
def change_column(self, table: str, column_name: str, field: peewee.Field):
|
||||
operations = [self.alter_change_column(table, column_name, field)]
|
||||
if not field.null:
|
||||
operations.extend([self.add_not_null(table, column_name)])
|
||||
return operations
|
||||
|
||||
def alter_change_column(self, table: str, column_name: str, field: peewee.Field) -> Operation:
|
||||
return self._update_column(table, column_name, lambda x, y: y)
|
||||
|
||||
@operation
|
||||
def sql(self, sql: str, *params) -> SQL:
|
||||
"""
|
||||
Executes raw SQL.
|
||||
"""
|
||||
return SQL(sql, *params)
|
||||
|
||||
def alter_add_column(
|
||||
self, table: str, column_name: str, field: peewee.Field, **kwargs) -> Operation:
|
||||
"""
|
||||
Fixes field name for ForeignKeys.
|
||||
"""
|
||||
name = field.name
|
||||
op = super().alter_add_column(
|
||||
table, column_name, field, **kwargs)
|
||||
if isinstance(field, peewee.ForeignKeyField):
|
||||
field.name = name
|
||||
return op
|
||||
|
||||
|
||||
class MigrationManager(object):
|
||||
|
||||
filemask = re.compile(r"[\d]+_[^\.]+\.py$")
|
||||
|
||||
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
|
||||
"""
|
||||
Initializes the migration manager.
|
||||
"""
|
||||
if not isinstance(database, (peewee.Database, peewee.Proxy)):
|
||||
raise RuntimeError('Invalid database: {}'.format(database))
|
||||
self.database = database
|
||||
|
||||
@cached_property
|
||||
def model(self) -> peewee.Model:
|
||||
"""
|
||||
Initialize and cache the MigrationHistory model.
|
||||
"""
|
||||
MigrateHistory._meta.database = self.database
|
||||
MigrateHistory._meta.table_name = 'migratehistory'
|
||||
MigrateHistory._meta.schema = None
|
||||
MigrateHistory.create_table(True)
|
||||
return MigrateHistory
|
||||
|
||||
@property
|
||||
def done(self) -> t.List[str]:
|
||||
"""
|
||||
Scans migrations in the database.
|
||||
"""
|
||||
return [mm.name for mm in self.model.select().order_by(self.model.id)]
|
||||
|
||||
@property
|
||||
def todo(self):
|
||||
"""
|
||||
Scans migrations in the file system.
|
||||
"""
|
||||
if not os.path.exists(helper.migration_dir):
|
||||
logger.warning('Migration directory: {} does not exist.'.format(
|
||||
helper.migration_dir))
|
||||
os.makedirs(helper.migration_dir)
|
||||
return sorted(f[:-3] for f in os.listdir(helper.migration_dir) if self.filemask.match(f))
|
||||
|
||||
@property
|
||||
def diff(self) -> t.List[str]:
|
||||
"""
|
||||
Calculates difference between the filesystem and the database.
|
||||
"""
|
||||
done = set(self.done)
|
||||
return [name for name in self.todo if name not in done]
|
||||
|
||||
@cached_property
|
||||
def migrator(self) -> Migrator:
|
||||
"""
|
||||
Create migrator and setup it with fake migrations.
|
||||
"""
|
||||
migrator = Migrator(self.database)
|
||||
for name in self.done:
|
||||
self.up_one(name, migrator, True)
|
||||
return migrator
|
||||
|
||||
def compile(self, name, migrate='', rollback=''):
|
||||
"""
|
||||
Compiles a migration.
|
||||
"""
|
||||
name = datetime.utcnow().strftime('%Y%m%d%H%M%S') + '_' + name
|
||||
filename = name + '.py'
|
||||
path = os.path.join(helper.migration_dir, filename)
|
||||
with open(path, 'w') as f:
|
||||
f.write(MIGRATE_TEMPLATE.format(
|
||||
migrate=migrate, rollback=rollback, name=filename))
|
||||
|
||||
return name
|
||||
|
||||
def create(self, name: str = 'auto', auto: bool = False) -> t.Optional[str]:
|
||||
"""
|
||||
Creates a migration.
|
||||
"""
|
||||
migrate = rollback = ''
|
||||
if auto:
|
||||
raise NotImplementedError
|
||||
|
||||
logger.info('Creating migration "{}"'.format(name))
|
||||
name = self.compile(name, migrate, rollback)
|
||||
logger.info('Migration has been created as "{}"'.format(name))
|
||||
return name
|
||||
|
||||
def clear(self):
|
||||
"""Clear migrations."""
|
||||
self.model.delete().execute()
|
||||
|
||||
def up(self, name: t.Optional[str] = None):
|
||||
"""
|
||||
Runs all unapplied migrations.
|
||||
"""
|
||||
logger.info('Starting migrations')
|
||||
console.info('Starting migrations')
|
||||
|
||||
done = []
|
||||
diff = self.diff
|
||||
if not diff:
|
||||
logger.info('There is nothing to migrate')
|
||||
console.info('There is nothing to migrate')
|
||||
return done
|
||||
|
||||
migrator = self.migrator
|
||||
for mname in diff:
|
||||
done.append(self.up_one(mname, self.migrator))
|
||||
if name and name == mname:
|
||||
break
|
||||
|
||||
return done
|
||||
|
||||
def read(self, name: str):
|
||||
"""
|
||||
Reads a migration from a file.
|
||||
"""
|
||||
call_params = dict()
|
||||
if os.name == 'nt' and sys.version_info >= (3, 0):
|
||||
# if system is windows - force utf-8 encoding
|
||||
call_params['encoding'] = 'utf-8'
|
||||
with open(os.path.join(helper.migration_dir, name + '.py'), **call_params) as f:
|
||||
code = f.read()
|
||||
scope = {}
|
||||
code = compile(code, '<string>', 'exec', dont_inherit=True)
|
||||
exec(code, scope, None)
|
||||
return scope.get('migrate', VOID), scope.get('rollback', VOID)
|
||||
|
||||
def up_one(self, name: str, migrator: Migrator,
|
||||
fake: bool = False, rollback: bool = False) -> str:
|
||||
"""
|
||||
Runs a migration with a given name.
|
||||
"""
|
||||
try:
|
||||
migrate_fn, rollback_fn = self.read(name)
|
||||
if fake:
|
||||
migrate_fn(migrator, self.database)
|
||||
migrator.clean()
|
||||
return name
|
||||
with self.database.transaction():
|
||||
if rollback:
|
||||
logger.info('Rolling back "{}"'.format(name))
|
||||
rollback_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
self.model.delete().where(self.model.name == name).execute()
|
||||
else:
|
||||
logger.info('Migrate "{}"'.format(name))
|
||||
migrate_fn(migrator, self.database)
|
||||
migrator.run()
|
||||
if name not in self.done:
|
||||
self.model.create(name=name)
|
||||
|
||||
logger.info('Done "{}"'.format(name))
|
||||
return name
|
||||
|
||||
except Exception:
|
||||
self.database.rollback()
|
||||
operation = 'Rollback' if rollback else 'Migration'
|
||||
logger.exception('{} failed: {}'.format(operation, name))
|
||||
raise
|
||||
|
||||
def down(self, name: t.Optional[str] = None):
|
||||
"""
|
||||
Rolls back migrations.
|
||||
"""
|
||||
if not self.done:
|
||||
raise RuntimeError('No migrations are found.')
|
||||
|
||||
name = self.done[-1]
|
||||
|
||||
migrator = self.migrator
|
||||
self.up_one(name, migrator, False, True)
|
||||
logger.warning('Rolled back migration: {}'.format(name))
|
@ -1,866 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger('peewee')
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
schema_version = (0, 1, 0) # major, minor, patch semver
|
||||
|
||||
database = SqliteDatabase(helper.db_path, pragmas={
|
||||
'journal_mode': 'wal',
|
||||
'cache_size': -1024 * 10})
|
||||
|
||||
class BaseModel(Model):
|
||||
class Meta:
|
||||
database = database
|
||||
|
||||
class SchemaVersion(BaseModel):
|
||||
# DO NOT EVER CHANGE THE SCHEMA OF THIS TABLE
|
||||
# (unless we have a REALLY good reason to)
|
||||
# There will only ever be one row, and it allows the database loader to detect
|
||||
# what it needs to do on major version upgrades so you don't have to wipe the DB
|
||||
# every time you upgrade
|
||||
schema_major = IntegerField()
|
||||
schema_minor = IntegerField()
|
||||
schema_patch = IntegerField()
|
||||
|
||||
class Meta:
|
||||
table_name = 'schema_version'
|
||||
primary_key = CompositeKey('schema_major', 'schema_minor', 'schema_patch')
|
||||
|
||||
|
||||
class Users(BaseModel):
|
||||
user_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
last_login = DateTimeField(default=datetime.datetime.now)
|
||||
last_update = DateTimeField(default=datetime.datetime.now)
|
||||
last_ip = CharField(default="")
|
||||
username = CharField(default="", unique=True, index=True)
|
||||
password = CharField(default="")
|
||||
enabled = BooleanField(default=True)
|
||||
superuser = BooleanField(default=False)
|
||||
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
|
||||
|
||||
class Roles(BaseModel):
|
||||
role_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
last_update = DateTimeField(default=datetime.datetime.now)
|
||||
role_name = CharField(default="", unique=True, index=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "roles"
|
||||
|
||||
|
||||
class User_Roles(BaseModel):
|
||||
user_id = ForeignKeyField(Users, backref='user_role')
|
||||
role_id = ForeignKeyField(Roles, backref='user_role')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_roles'
|
||||
primary_key = CompositeKey('user_id', 'role_id')
|
||||
|
||||
|
||||
class Audit_Log(BaseModel):
|
||||
audit_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
user_name = CharField(default="")
|
||||
user_id = IntegerField(default=0, index=True)
|
||||
source_ip = CharField(default='127.0.0.1')
|
||||
server_id = IntegerField(default=None, index=True) # When auditing global events, use server ID 0
|
||||
log_msg = TextField(default='')
|
||||
|
||||
|
||||
class Host_Stats(BaseModel):
|
||||
time = DateTimeField(default=datetime.datetime.now, index=True)
|
||||
boot_time = CharField(default="")
|
||||
cpu_usage = FloatField(default=0)
|
||||
cpu_cores = IntegerField(default=0)
|
||||
cpu_cur_freq = FloatField(default=0)
|
||||
cpu_max_freq = FloatField(default=0)
|
||||
mem_percent = FloatField(default=0)
|
||||
mem_usage = CharField(default="")
|
||||
mem_total = CharField(default="")
|
||||
disk_json = TextField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = "host_stats"
|
||||
|
||||
|
||||
class Servers(BaseModel):
|
||||
server_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_uuid = CharField(default="", index=True)
|
||||
server_name = CharField(default="Server", index=True)
|
||||
path = CharField(default="")
|
||||
backup_path = CharField(default="")
|
||||
executable = CharField(default="")
|
||||
log_path = CharField(default="")
|
||||
execution_command = CharField(default="")
|
||||
auto_start = BooleanField(default=0)
|
||||
auto_start_delay = IntegerField(default=10)
|
||||
crash_detection = BooleanField(default=0)
|
||||
stop_command = CharField(default="stop")
|
||||
server_ip = CharField(default="127.0.0.1")
|
||||
server_port = IntegerField(default=25565)
|
||||
logs_delete_after = IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
table_name = "servers"
|
||||
|
||||
class Role_Servers(BaseModel):
|
||||
role_id = ForeignKeyField(Roles, backref='role_server')
|
||||
server_id = ForeignKeyField(Servers, backref='role_server')
|
||||
|
||||
class Meta:
|
||||
table_name = 'role_servers'
|
||||
primary_key = CompositeKey('role_id', 'server_id')
|
||||
|
||||
|
||||
class Server_Stats(BaseModel):
|
||||
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="")
|
||||
|
||||
|
||||
class Meta:
|
||||
table_name = "server_stats"
|
||||
|
||||
|
||||
class Commands(BaseModel):
|
||||
command_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_id = ForeignKeyField(Servers, backref='server', index=True)
|
||||
user = ForeignKeyField(Users, backref='user', index=True)
|
||||
source_ip = CharField(default='127.0.0.1')
|
||||
command = CharField(default='')
|
||||
executed = BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "commands"
|
||||
|
||||
|
||||
class Webhooks(BaseModel):
|
||||
id = AutoField()
|
||||
name = CharField(max_length=64, unique=True, index=True)
|
||||
method = CharField(default="POST")
|
||||
url = CharField(unique=True)
|
||||
event = CharField(default="")
|
||||
send_data = BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "webhooks"
|
||||
|
||||
class Schedules(BaseModel):
|
||||
schedule_id = IntegerField(unique=True, primary_key=True)
|
||||
server_id = ForeignKeyField(Servers, backref='schedule_server')
|
||||
enabled = BooleanField()
|
||||
action = CharField()
|
||||
interval = IntegerField()
|
||||
interval_type = CharField()
|
||||
start_time = CharField(null=True)
|
||||
command = CharField(null=True)
|
||||
comment = CharField()
|
||||
|
||||
class Meta:
|
||||
table_name = 'schedules'
|
||||
|
||||
class Backups(BaseModel):
|
||||
directories = CharField(null=True)
|
||||
max_backups = IntegerField()
|
||||
server_id = ForeignKeyField(Servers, backref='backups_server')
|
||||
schedule_id = ForeignKeyField(Schedules, backref='backups_schedule')
|
||||
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
|
||||
|
||||
class db_builder:
|
||||
|
||||
@staticmethod
|
||||
def create_tables():
|
||||
with database:
|
||||
database.create_tables([
|
||||
Backups,
|
||||
Users,
|
||||
Roles,
|
||||
User_Roles,
|
||||
Host_Stats,
|
||||
Webhooks,
|
||||
Servers,
|
||||
Role_Servers,
|
||||
Server_Stats,
|
||||
Commands,
|
||||
Audit_Log,
|
||||
SchemaVersion,
|
||||
Schedules
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def default_settings():
|
||||
logger.info("Fresh Install Detected - Creating Default Settings")
|
||||
console.info("Fresh Install Detected - Creating Default Settings")
|
||||
SchemaVersion.insert({
|
||||
SchemaVersion.schema_major: schema_version[0],
|
||||
SchemaVersion.schema_minor: schema_version[1],
|
||||
SchemaVersion.schema_patch: schema_version[2]
|
||||
}).execute()
|
||||
default_data = helper.find_default_password()
|
||||
|
||||
username = default_data.get("username", 'admin')
|
||||
password = default_data.get("password", 'crafty')
|
||||
#api_token = helper.random_string_generator(32)
|
||||
#
|
||||
#Users.insert({
|
||||
# Users.username: username.lower(),
|
||||
# Users.password: helper.encode_pass(password),
|
||||
# Users.api_token: api_token,
|
||||
# Users.enabled: True,
|
||||
# Users.superuser: True
|
||||
#}).execute()
|
||||
db_shortcuts.add_user(username, password=password, superuser=True)
|
||||
|
||||
#console.info("API token is {}".format(api_token))
|
||||
|
||||
@staticmethod
|
||||
def is_fresh_install():
|
||||
try:
|
||||
user = Users.get_by_id(1)
|
||||
return False
|
||||
except:
|
||||
return True
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def check_schema_version():
|
||||
svs = SchemaVersion.select().execute()
|
||||
if len(svs) != 1:
|
||||
raise exceptions.SchemaError("Multiple or no schema versions detected - potentially a failed upgrade?")
|
||||
sv = svs[0]
|
||||
svt = (sv.schema_major, sv.schema_minor, sv.schema_patch)
|
||||
logger.debug("Schema: found {}, expected {}".format(svt, schema_version))
|
||||
console.debug("Schema: found {}, expected {}".format(svt, schema_version))
|
||||
if sv.schema_major > schema_version[0]:
|
||||
raise exceptions.SchemaError("Major version mismatch - possible code reversion")
|
||||
elif sv.schema_major < schema_version[0]:
|
||||
db_shortcuts.upgrade_schema()
|
||||
|
||||
if sv.schema_minor > schema_version[1]:
|
||||
logger.warning("Schema minor mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
console.warning("Schema minor mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
elif sv.schema_minor < schema_version[1]:
|
||||
db_shortcuts.upgrade_schema()
|
||||
|
||||
if sv.schema_patch > schema_version[2]:
|
||||
logger.info("Schema patch mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
console.info("Schema patch mismatch detected: found {}, expected {}. Proceed with caution".format(svt, schema_version))
|
||||
elif sv.schema_patch < schema_version[2]:
|
||||
db_shortcuts.upgrade_schema()
|
||||
logger.info("Schema validation successful! {}".format(schema_version))
|
||||
|
||||
class db_shortcuts:
|
||||
|
||||
@staticmethod
|
||||
def upgrade_schema():
|
||||
raise NotImplemented("I don't know who you are or how you reached this code, but this should NOT have happened. Please report it to the developer with due haste.")
|
||||
|
||||
@staticmethod
|
||||
def return_rows(query):
|
||||
rows = []
|
||||
|
||||
try:
|
||||
if query.count() > 0:
|
||||
for s in query:
|
||||
rows.append(model_to_dict(s))
|
||||
except Exception as e:
|
||||
logger.warning("Database Error: {}".format(e))
|
||||
pass
|
||||
|
||||
return rows
|
||||
|
||||
@staticmethod
|
||||
def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565):
|
||||
return Servers.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.stop_command: server_stop,
|
||||
Servers.backup_path: backup_path
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def remove_server(server_id):
|
||||
with database.atomic():
|
||||
Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
|
||||
Servers.delete().where(Servers.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_server_data_by_id(server_id):
|
||||
query = Servers.select().where(Servers.server_id == server_id).limit(1)
|
||||
try:
|
||||
return db_helper.return_rows(query)[0]
|
||||
except IndexError:
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def get_all_defined_servers():
|
||||
query = Servers.select()
|
||||
return db_helper.return_rows(query)
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers(user_id):
|
||||
user_servers = User_Servers.select().where(User_Servers.user_id == user_id)
|
||||
server_data = []
|
||||
|
||||
for u in user_servers:
|
||||
server_data.append(db_helper.get_server_data_by_id(u.server_id))
|
||||
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_from_roles(user_id):
|
||||
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
roles_list = []
|
||||
role_server = []
|
||||
server_data = []
|
||||
|
||||
for u in user_roles:
|
||||
roles_list.append(db_helper.get_role(u.role_id))
|
||||
|
||||
for r in roles_list:
|
||||
role_test = Role_Servers.select().where(Role_Servers.role_id == r.get('role_id'))
|
||||
for t in role_test:
|
||||
role_server.append(t)
|
||||
|
||||
for s in role_server:
|
||||
server_data.append(db_helper.get_server_data_by_id(s.server_id))
|
||||
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_all_servers_stats():
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
server_data = []
|
||||
|
||||
for s in servers:
|
||||
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
|
||||
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0]})
|
||||
return server_data
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats(user_id):
|
||||
user_servers = User_Servers.select().where(User_Servers.user_id == user_id)
|
||||
authorized_servers = []
|
||||
server_data = []
|
||||
|
||||
for u in user_servers:
|
||||
authorized_servers.append(db_helper.get_server_data_by_id(u.server_id))
|
||||
|
||||
for s in authorized_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)})
|
||||
return server_data
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_authorized_servers_stats_from_roles(user_id):
|
||||
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
roles_list = []
|
||||
role_server = []
|
||||
authorized_servers = []
|
||||
server_data = []
|
||||
|
||||
for u in user_roles:
|
||||
roles_list.append(db_helper.get_role(u.role_id))
|
||||
|
||||
for r in roles_list:
|
||||
role_test = Role_Servers.select().where(Role_Servers.role_id == r.get('role_id'))
|
||||
for t in role_test:
|
||||
role_server.append(t)
|
||||
|
||||
for s in role_server:
|
||||
authorized_servers.append(db_helper.get_server_data_by_id(s.server_id))
|
||||
|
||||
for s in authorized_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]})
|
||||
return server_data
|
||||
|
||||
@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 db_helper.get_server_data_by_id(server_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def server_id_authorized(serverId, user_id):
|
||||
userServer = User_Servers.select().where(User_Servers.server_id == serverId)
|
||||
authorized = userServer.select().where(User_Servers.user_id == user_id)
|
||||
#authorized = db_helper.return_rows(authorized)
|
||||
|
||||
if authorized.count() == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def server_id_authorized_from_roles(serverId, user_id):
|
||||
cpt_authorized = 0
|
||||
roles_list = []
|
||||
role_server = []
|
||||
authorized = []
|
||||
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
|
||||
|
||||
for u in user_roles:
|
||||
roles_list.append(db_helper.get_role(u.role_id))
|
||||
|
||||
for r in roles_list:
|
||||
role_test = Role_Servers.select().where(Role_Servers.role_id == r.get('role_id'))
|
||||
|
||||
for s in role_test:
|
||||
if s.server_id.server_id == serverId:
|
||||
cpt_authorized += 1
|
||||
|
||||
if cpt_authorized == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_latest_hosts_stats():
|
||||
query = Host_Stats.select().order_by(Host_Stats.id.desc()).get()
|
||||
return model_to_dict(query)
|
||||
|
||||
@staticmethod
|
||||
def new_api_token():
|
||||
while True:
|
||||
token = helper.random_string_generator(32)
|
||||
test = list(Users.select(Users.user_id).where(Users.api_token == token))
|
||||
if len(test) == 0:
|
||||
return token
|
||||
|
||||
@staticmethod
|
||||
def get_all_users():
|
||||
query = Users.select()
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
def get_all_roles():
|
||||
query = Roles.select()
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_by_name(username):
|
||||
if username == "SYSTEM":
|
||||
return 0
|
||||
try:
|
||||
return (Users.get(Users.username == username)).user_id
|
||||
except DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_user(user_id):
|
||||
if user_id == 0:
|
||||
return {
|
||||
user_id: 0,
|
||||
created: None,
|
||||
last_login: None,
|
||||
last_update: None,
|
||||
last_ip: "127.27.23.89",
|
||||
username: "SYSTEM",
|
||||
password: None,
|
||||
enabled: True,
|
||||
superuser: False,
|
||||
api_token: None,
|
||||
roles: [],
|
||||
servers: []
|
||||
}
|
||||
user = model_to_dict(Users.get(Users.user_id == user_id))
|
||||
|
||||
if user:
|
||||
roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id)
|
||||
# TODO: this query needs to be narrower
|
||||
roles = set()
|
||||
for r in roles_query:
|
||||
roles.add(r.role_id.role_id)
|
||||
#servers_query = User_Servers.select().join(Servers, JOIN.INNER).where(User_Servers.user_id == user_id)
|
||||
## TODO: this query needs to be narrower
|
||||
servers = set()
|
||||
#for s in servers_query:
|
||||
# servers.add(s.server_id.server_id)
|
||||
user['roles'] = roles
|
||||
#user['servers'] = servers
|
||||
#logger.debug("user: ({}) {}".format(user_id, user))
|
||||
return user
|
||||
else:
|
||||
#logger.debug("user: ({}) {}".format(user_id, {}))
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def update_user(user_id, user_data={}):
|
||||
base_data = db_helper.get_user(user_id)
|
||||
up_data = {}
|
||||
added_roles = set()
|
||||
removed_roles = set()
|
||||
added_servers = set()
|
||||
removed_servers = set()
|
||||
for key in user_data:
|
||||
if key == "user_id":
|
||||
continue
|
||||
elif key == "roles":
|
||||
added_roles = user_data['roles'].difference(base_data['roles'])
|
||||
removed_roles = base_data['roles'].difference(user_data['roles'])
|
||||
#elif key == "servers":
|
||||
# added_servers = user_data['servers'].difference(base_data['servers'])
|
||||
# removed_servers = base_data['servers'].difference(user_data['servers'])
|
||||
elif key == "regen_api":
|
||||
if user_data['regen_api']:
|
||||
up_data['api_token'] = db_shortcuts.new_api_token()
|
||||
elif key == "password":
|
||||
if user_data['password'] is not None and user_data['password'] != "":
|
||||
up_data['password'] = helper.encode_pass(user_data['password'])
|
||||
elif base_data[key] != user_data[key]:
|
||||
up_data[key] = user_data[key]
|
||||
up_data['last_update'] = helper.get_time_as_string()
|
||||
logger.debug("user: {} +role:{} -role:{} +server:{} -server{}".format(user_data, added_roles, removed_roles, added_servers, removed_servers))
|
||||
with database.atomic():
|
||||
for role in added_roles:
|
||||
User_Roles.get_or_create(user_id=user_id, role_id=role)
|
||||
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
|
||||
User_Roles.delete().where(User_Roles.user_id == user_id).where(User_Roles.role_id.in_(removed_roles)).execute()
|
||||
|
||||
#for server in added_servers:
|
||||
# User_Servers.get_or_create(user_id=user_id, server_id=server)
|
||||
# # TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
|
||||
#User_Servers.delete().where(User_Servers.user_id == user_id).where(User_Servers.server_id.in_(removed_servers)).execute()
|
||||
if up_data:
|
||||
Users.update(up_data).where(Users.user_id == user_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
|
||||
if password is not None:
|
||||
pw_enc = helper.encode_pass(password)
|
||||
else:
|
||||
pw_enc = None
|
||||
if api_token is None:
|
||||
api_token = db_shortcuts.new_api_token()
|
||||
else:
|
||||
if type(api_token) is not str and len(api_token) != 32:
|
||||
raise ValueError("API token must be a 32 character string")
|
||||
user_id = Users.insert({
|
||||
Users.username: username.lower(),
|
||||
Users.password: pw_enc,
|
||||
Users.api_token: api_token,
|
||||
Users.enabled: enabled,
|
||||
Users.superuser: superuser,
|
||||
Users.created: helper.get_time_as_string()
|
||||
}).execute()
|
||||
return user_id
|
||||
|
||||
@staticmethod
|
||||
def remove_user(user_id):
|
||||
with database.atomic():
|
||||
User_Roles.delete().where(User_Roles.user_id == user_id).execute()
|
||||
user = Users.get(Users.user_id == user_id)
|
||||
return user.delete_instance()
|
||||
|
||||
@staticmethod
|
||||
def user_id_exists(user_id):
|
||||
if not db_shortcuts.get_user(user_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_roleid_by_name(role_name):
|
||||
try:
|
||||
return (Roles.get(Roles.role_name == role_name)).role_id
|
||||
except DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_role(role_id):
|
||||
role = model_to_dict(Roles.get(Roles.role_id == role_id))
|
||||
|
||||
if role:
|
||||
servers_query = Role_Servers.select().join(Servers, JOIN.INNER).where(Role_Servers.role_id == role_id)
|
||||
# TODO: this query needs to be narrower
|
||||
servers = set()
|
||||
for s in servers_query:
|
||||
servers.add(s.server_id.server_id)
|
||||
role['servers'] = servers
|
||||
#logger.debug("role: ({}) {}".format(role_id, role))
|
||||
return role
|
||||
else:
|
||||
#logger.debug("role: ({}) {}".format(role_id, {}))
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def update_role(role_id, role_data={}):
|
||||
base_data = db_helper.get_role(role_id)
|
||||
up_data = {}
|
||||
added_servers = set()
|
||||
removed_servers = set()
|
||||
for key in role_data:
|
||||
if key == "role_id":
|
||||
continue
|
||||
elif key == "servers":
|
||||
added_servers = role_data['servers'].difference(base_data['servers'])
|
||||
removed_servers = base_data['servers'].difference(role_data['servers'])
|
||||
elif base_data[key] != role_data[key]:
|
||||
up_data[key] = role_data[key]
|
||||
up_data['last_update'] = helper.get_time_as_string()
|
||||
logger.debug("role: {} +server:{} -server{}".format(role_data, added_servers, removed_servers))
|
||||
with database.atomic():
|
||||
for server in added_servers:
|
||||
Role_Servers.get_or_create(role_id=role_id, server_id=server)
|
||||
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
|
||||
Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute()
|
||||
if up_data:
|
||||
Roles.update(up_data).where(Roles.role_id == role_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def add_role(role_name):
|
||||
role_id = Roles.insert({
|
||||
Roles.role_name: role_name.lower(),
|
||||
Roles.created: helper.get_time_as_string()
|
||||
}).execute()
|
||||
return role_id
|
||||
|
||||
@staticmethod
|
||||
def remove_role(role_id):
|
||||
with database.atomic():
|
||||
Role_Servers.delete().where(Role_Servers.role_id == role_id).execute()
|
||||
User_Roles.delete().where(User_Roles.role_id == role_id).execute()
|
||||
role = Roles.get(Roles.role_id == role_id)
|
||||
return role.delete_instance()
|
||||
|
||||
@staticmethod
|
||||
def role_id_exists(role_id):
|
||||
if not db_shortcuts.get_role(role_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_unactioned_commands():
|
||||
query = Commands.select().where(Commands.executed == 0)
|
||||
return db_helper.return_rows(query)
|
||||
|
||||
@staticmethod
|
||||
def get_server_friendly_name(server_id):
|
||||
server_data = db_helper.get_server_data_by_id(server_id)
|
||||
friendly_name = "{} with ID: {}".format(server_data.get('server_name', None), server_data.get('server_id', 0))
|
||||
return friendly_name
|
||||
|
||||
@staticmethod
|
||||
def send_command(user_id, server_id, remote_ip, command):
|
||||
|
||||
server_name = db_helper.get_server_friendly_name(server_id)
|
||||
|
||||
# Example: Admin issued command start_server for server Survival
|
||||
db_helper.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name),
|
||||
server_id, remote_ip)
|
||||
|
||||
Commands.insert({
|
||||
Commands.server_id: server_id,
|
||||
Commands.user: user_id,
|
||||
Commands.source_ip: remote_ip,
|
||||
Commands.command: command
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_actity_log():
|
||||
q = Audit_Log.select()
|
||||
return db_helper.return_db_rows(q)
|
||||
|
||||
@staticmethod
|
||||
def return_db_rows(model):
|
||||
data = [model_to_dict(row) for row in model]
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def mark_command_complete(command_id=None):
|
||||
if command_id is not None:
|
||||
logger.debug("Marking Command {} completed".format(command_id))
|
||||
Commands.update({
|
||||
Commands.executed: True
|
||||
}).where(Commands.command_id == command_id).execute()
|
||||
|
||||
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
|
||||
logger.debug("Adding to audit log User:{} - Message: {} ".format(user_id, log_msg))
|
||||
user_data = self.get_user(user_id)
|
||||
|
||||
audit_msg = "{} {}".format(str(user_data['username']).capitalize(), log_msg)
|
||||
|
||||
websocket_helper.broadcast('notification', audit_msg)
|
||||
|
||||
Audit_Log.insert({
|
||||
Audit_Log.user_name: user_data['username'],
|
||||
Audit_Log.user_id: user_id,
|
||||
Audit_Log.server_id: server_id,
|
||||
Audit_Log.log_msg: audit_msg,
|
||||
Audit_Log.source_ip: source_ip
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip):
|
||||
Audit_Log.insert({
|
||||
Audit_Log.user_name: user_name,
|
||||
Audit_Log.user_id: user_id,
|
||||
Audit_Log.server_id: server_id,
|
||||
Audit_Log.log_msg: log_msg,
|
||||
Audit_Log.source_ip: source_ip
|
||||
}).execute()
|
||||
|
||||
@staticmethod
|
||||
def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True):
|
||||
sch_id = Schedules.insert({
|
||||
Schedules.server_id: server_id,
|
||||
Schedules.action: action,
|
||||
Schedules.enabled: enabled,
|
||||
Schedules.interval: interval,
|
||||
Schedules.interval_type: interval_type,
|
||||
Schedules.start_time: start_time,
|
||||
Schedules.command: command,
|
||||
Schedules.comment: comment
|
||||
}).execute()
|
||||
return sch_id
|
||||
|
||||
@staticmethod
|
||||
def delete_scheduled_task(schedule_id):
|
||||
sch = Schedules.get(Schedules.schedule_id == schedule_id)
|
||||
return Schedules.delete_instance(sch)
|
||||
|
||||
@staticmethod
|
||||
def update_scheduled_task(schedule_id, updates):
|
||||
Schedules.update(updates).where(Schedules.schedule_id == schedule_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_scheduled_task(schedule_id):
|
||||
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_by_server(server_id):
|
||||
return Schedules.select().where(Schedules.server_id == server_id).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_all():
|
||||
return Schedules.select().execute()
|
||||
|
||||
@staticmethod
|
||||
def get_schedules_enabled():
|
||||
return Schedules.select().where(Schedules.enabled == True).execute()
|
||||
|
||||
@staticmethod
|
||||
def get_backup_config(server_id):
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
conf = {
|
||||
"backup_path": row.server_id.backup_path,
|
||||
"directories": row.directories,
|
||||
"max_backups": row.max_backups,
|
||||
"auto_enabled": row.schedule_id.enabled,
|
||||
"server_id": row.server_id.server_id
|
||||
}
|
||||
except IndexError:
|
||||
conf = {
|
||||
"backup_path": None,
|
||||
"directories": None,
|
||||
"max_backups": 0,
|
||||
"auto_enabled": True,
|
||||
"server_id": server_id
|
||||
}
|
||||
return conf
|
||||
|
||||
@staticmethod
|
||||
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True):
|
||||
logger.debug("Updating server {} backup config with {}".format(server_id, locals()))
|
||||
try:
|
||||
row = Backups.select().where(Backups.server_id == server_id).join(Schedules).join(Servers)[0]
|
||||
new_row = False
|
||||
conf = {}
|
||||
schd = {}
|
||||
except IndexError:
|
||||
conf = {
|
||||
"directories": None,
|
||||
"max_backups": 0,
|
||||
"server_id": server_id
|
||||
}
|
||||
schd = {
|
||||
"enabled": True,
|
||||
"action": "backup_server",
|
||||
"interval_type": "days",
|
||||
"interval": 1,
|
||||
"start_time": "00:00",
|
||||
"server_id": server_id,
|
||||
"comment": "Default backup job"
|
||||
}
|
||||
new_row = True
|
||||
if max_backups is not None:
|
||||
conf['max_backups'] = max_backups
|
||||
schd['enabled'] = bool(auto_enabled)
|
||||
if not new_row:
|
||||
with database.atomic():
|
||||
if backup_path is not None:
|
||||
u1 = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id).execute()
|
||||
else:
|
||||
u1 = 0
|
||||
u2 = Backups.update(conf).where(Backups.server_id == server_id).execute()
|
||||
u3 = Schedules.update(schd).where(Schedules.schedule_id == row.schedule_id).execute()
|
||||
logger.debug("Updating existing backup record. {}+{}+{} rows affected".format(u1, u2, u3))
|
||||
else:
|
||||
with database.atomic():
|
||||
conf["server_id"] = server_id
|
||||
if backup_path is not None:
|
||||
u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
|
||||
s = Schedules.create(**schd)
|
||||
conf['schedule_id'] = s.schedule_id
|
||||
b = Backups.create(**conf)
|
||||
logger.debug("Creating new backup record.")
|
||||
|
||||
installer = db_builder()
|
||||
db_helper = db_shortcuts()
|
@ -3,38 +3,105 @@ import sys
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import psutil
|
||||
import pexpect
|
||||
import datetime
|
||||
import threading
|
||||
import schedule
|
||||
import logging.config
|
||||
import zipfile
|
||||
from threading import Thread
|
||||
import shutil
|
||||
import subprocess
|
||||
import zlib
|
||||
import html
|
||||
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import db_helper, Servers
|
||||
from app.classes.models.servers import Servers, servers_helper
|
||||
from app.classes.models.management import management_helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.shared.translation import translation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
try:
|
||||
import pexpect
|
||||
import psutil
|
||||
#import pexpect
|
||||
import schedule
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class ServerOutBuf:
|
||||
lines = {}
|
||||
|
||||
def __init__(self, proc, server_id):
|
||||
self.proc = proc
|
||||
self.server_id = str(server_id)
|
||||
# Buffers text for virtual_terminal_lines config number of lines
|
||||
self.max_lines = helper.get_setting('virtual_terminal_lines')
|
||||
self.line_buffer = ''
|
||||
ServerOutBuf.lines[self.server_id] = []
|
||||
self.lsi = 0
|
||||
|
||||
def process_byte(self, char):
|
||||
if char == os.linesep[self.lsi]:
|
||||
self.lsi += 1
|
||||
else:
|
||||
self.lsi = 0
|
||||
self.line_buffer += char
|
||||
|
||||
if self.lsi >= len(os.linesep):
|
||||
self.lsi = 0
|
||||
ServerOutBuf.lines[self.server_id].append(self.line_buffer)
|
||||
|
||||
self.new_line_handler(self.line_buffer)
|
||||
self.line_buffer = ''
|
||||
# Limit list length to self.max_lines:
|
||||
if len(ServerOutBuf.lines[self.server_id]) > self.max_lines:
|
||||
ServerOutBuf.lines[self.server_id].pop(0)
|
||||
|
||||
def check(self):
|
||||
while True:
|
||||
if self.proc.poll() is None:
|
||||
char = self.proc.stdout.read(1).decode('utf-8')
|
||||
# TODO: we may want to benchmark reading in blocks and userspace processing it later, reads are kind of expensive as a syscall
|
||||
self.process_byte(char)
|
||||
else:
|
||||
flush = self.proc.stdout.read().decode('utf-8')
|
||||
for char in flush:
|
||||
self.process_byte(char)
|
||||
break
|
||||
|
||||
def new_line_handler(self, new_line):
|
||||
new_line = re.sub('(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )', '', new_line)
|
||||
new_line = re.sub('[A-z]{2}\b\b', '', new_line)
|
||||
highlighted = helper.log_colors(html.escape(new_line))
|
||||
|
||||
logger.debug('Broadcasting new virtual terminal line')
|
||||
|
||||
# TODO: Do not send data to clients who do not have permission to view this server's console
|
||||
websocket_helper.broadcast_page_params(
|
||||
'/panel/server_detail',
|
||||
{
|
||||
'id': self.server_id
|
||||
},
|
||||
'vterm_new_line',
|
||||
{
|
||||
'line': highlighted + '<br />'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class Server:
|
||||
|
||||
def __init__(self, stats):
|
||||
# holders for our process
|
||||
self.process = None
|
||||
self.line = False
|
||||
self.PID = None
|
||||
self.start_time = None
|
||||
self.server_command = None
|
||||
self.server_path = None
|
||||
@ -42,14 +109,17 @@ class Server:
|
||||
self.settings = None
|
||||
self.updating = False
|
||||
self.server_id = None
|
||||
self.jar_update_url = None
|
||||
self.name = None
|
||||
self.is_crashed = False
|
||||
self.restart_count = 0
|
||||
self.crash_watcher_schedule = None
|
||||
self.stats = stats
|
||||
self.backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name=f"backup_{self.name}")
|
||||
self.is_backingup = False
|
||||
|
||||
def reload_server_settings(self):
|
||||
server_data = db_helper.get_server_data_by_id(self.server_id)
|
||||
server_data = servers_helper.get_server_data_by_id(self.server_id)
|
||||
self.settings = server_data
|
||||
|
||||
def do_server_setup(self, server_data_obj):
|
||||
@ -63,7 +133,6 @@ class Server:
|
||||
self.settings = server_data_obj
|
||||
|
||||
# build our server run command
|
||||
self.setup_server_run_command()
|
||||
|
||||
if server_data_obj['auto_start']:
|
||||
delay = int(self.settings['auto_start_delay'])
|
||||
@ -81,15 +150,15 @@ class Server:
|
||||
# remove the scheduled job since it's ran
|
||||
return schedule.CancelJob
|
||||
|
||||
def run_threaded_server(self):
|
||||
def run_threaded_server(self, lang):
|
||||
# start the server
|
||||
self.server_thread = threading.Thread(target=self.start_server, daemon=True)
|
||||
self.server_thread = threading.Thread(target=self.start_server, daemon=True, args=(lang,), name='{}_server_thread'.format(self.server_id))
|
||||
self.server_thread.start()
|
||||
|
||||
def setup_server_run_command(self):
|
||||
# configure the server
|
||||
server_exec_path = self.settings['executable']
|
||||
self.server_command = self.settings['execution_command']
|
||||
self.server_command = helper.cmdparse(self.settings['execution_command'])
|
||||
self.server_path = self.settings['path']
|
||||
|
||||
# let's do some quick checking to make sure things actually exists
|
||||
@ -97,7 +166,6 @@ class Server:
|
||||
if not helper.check_file_exists(full_path):
|
||||
logger.critical("Server executable path: {} does not seem to exist".format(full_path))
|
||||
console.critical("Server executable path: {} does not seem to exist".format(full_path))
|
||||
helper.do_exit()
|
||||
|
||||
if not helper.check_path_exists(self.server_path):
|
||||
logger.critical("Server path: {} does not seem to exits".format(self.server_path))
|
||||
@ -109,39 +177,72 @@ class Server:
|
||||
console.warning("Unable to write/access {}".format(self.server_path))
|
||||
helper.do_exit()
|
||||
|
||||
def start_server(self):
|
||||
def start_server(self, user_lang):
|
||||
|
||||
logger.info("Start command detected. Reloading settings from DB for server {}".format(self.name))
|
||||
self.setup_server_run_command()
|
||||
# fail safe in case we try to start something already running
|
||||
if self.check_running():
|
||||
logger.error("Server is already running - Cancelling Startup")
|
||||
console.error("Server is already running - Cancelling Startup")
|
||||
return False
|
||||
if self.check_update():
|
||||
logger.error("Server is updating. Terminating startup.")
|
||||
return False
|
||||
|
||||
logger.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
console.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
|
||||
if os.name == "nt":
|
||||
logger.info("Windows Detected")
|
||||
self.server_command = self.server_command.replace('\\', '/')
|
||||
creationflags=subprocess.CREATE_NEW_CONSOLE
|
||||
else:
|
||||
logger.info("Linux Detected")
|
||||
logger.info("Unix Detected")
|
||||
creationflags=None
|
||||
|
||||
logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command))
|
||||
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
|
||||
|
||||
servers_helper.set_waiting_start(self.server_id, False)
|
||||
try:
|
||||
self.process = subprocess.Popen(self.server_command, cwd=self.server_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
except Exception as ex:
|
||||
msg = "Server {} failed to start with error code: {}".format(self.name, ex)
|
||||
logger.error(msg)
|
||||
websocket_helper.broadcast('send_start_error', {
|
||||
'error': translation.translate('error', 'start-error', user_lang).format(self.name, ex)
|
||||
})
|
||||
return False
|
||||
if helper.check_internet():
|
||||
loc_server_port = servers_helper.get_server_stats_by_id(self.server_id)['server_port']
|
||||
if helper.check_port(loc_server_port):
|
||||
websocket_helper.broadcast('send_start_reload', {
|
||||
})
|
||||
else:
|
||||
websocket_helper.broadcast('send_start_error', {
|
||||
'error': translation.translate('error', 'closedPort', user_lang).format(loc_server_port)
|
||||
})
|
||||
else:
|
||||
websocket_helper.broadcast('send_start_error', {
|
||||
'error': translation.translate('error', 'internet', user_lang)
|
||||
})
|
||||
servers_helper.set_waiting_start(self.server_id, False)
|
||||
out_buf = ServerOutBuf(self.process, self.server_id)
|
||||
|
||||
logger.debug('Starting virtual terminal listener for server {}'.format(self.name))
|
||||
threading.Thread(target=out_buf.check, daemon=True, name='{}_virtual_terminal'.format(self.server_id)).start()
|
||||
|
||||
self.is_crashed = False
|
||||
|
||||
ts = time.time()
|
||||
self.start_time = str(datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S'))
|
||||
self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
|
||||
|
||||
if psutil.pid_exists(self.process.pid):
|
||||
self.PID = self.process.pid
|
||||
logger.info("Server {} running with PID {}".format(self.name, self.PID))
|
||||
console.info("Server {} running with PID {}".format(self.name, self.PID))
|
||||
if self.process.poll() is None:
|
||||
logger.info("Server {} running with PID {}".format(self.name, self.process.pid))
|
||||
console.info("Server {} running with PID {}".format(self.name, self.process.pid))
|
||||
self.is_crashed = False
|
||||
self.stats.record_stats()
|
||||
else:
|
||||
logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.PID))
|
||||
console.warning("Server PID {} died right after starting - is this a server config issue?".format(self.PID))
|
||||
logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid))
|
||||
console.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid))
|
||||
|
||||
if self.settings['crash_detection']:
|
||||
logger.info("Server {} has crash detection enabled - starting watcher task".format(self.name))
|
||||
@ -158,51 +259,53 @@ class Server:
|
||||
def stop_server(self):
|
||||
if self.settings['stop_command']:
|
||||
self.send_command(self.settings['stop_command'])
|
||||
|
||||
running = self.check_running()
|
||||
x = 0
|
||||
|
||||
# caching the name and pid number
|
||||
server_name = self.name
|
||||
server_pid = self.PID
|
||||
|
||||
while running:
|
||||
x = x+1
|
||||
logger.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
console.info("Server {} is still running - waiting 2s to see if it stops".format(server_name))
|
||||
console.info("Server has {} seconds to respond before we force it down".format(int(60-(x*2))))
|
||||
running = self.check_running()
|
||||
time.sleep(2)
|
||||
|
||||
# if we haven't closed in 60 seconds, let's just slam down on the PID
|
||||
if x >= 30:
|
||||
logger.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
console.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
self.process.terminate(force=True)
|
||||
|
||||
logger.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
console.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
|
||||
else:
|
||||
self.process.terminate(force=True)
|
||||
#windows will need to be handled separately for Ctrl+C
|
||||
self.process.terminate()
|
||||
running = self.check_running()
|
||||
if not running:
|
||||
logger.info("Can't stop server {} if it's not running".format(self.name))
|
||||
console.info("Can't stop server {} if it's not running".format(self.name))
|
||||
return
|
||||
x = 0
|
||||
|
||||
# caching the name and pid number
|
||||
server_name = self.name
|
||||
server_pid = self.process.pid
|
||||
|
||||
while running:
|
||||
x = x+1
|
||||
logstr = "Server {} is still running - waiting 2s to see if it stops ({} seconds until force close)".format(server_name, int(60-(x*2)))
|
||||
logger.info(logstr)
|
||||
console.info(logstr)
|
||||
running = self.check_running()
|
||||
time.sleep(2)
|
||||
|
||||
# if we haven't closed in 60 seconds, let's just slam down on the PID
|
||||
if x >= 30:
|
||||
logger.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
console.info("Server {} is still running - Forcing the process down".format(server_name))
|
||||
self.kill()
|
||||
|
||||
logger.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
console.info("Stopped Server {} with PID {}".format(server_name, server_pid))
|
||||
|
||||
# massive resetting of variables
|
||||
self.cleanup_server_object()
|
||||
|
||||
self.stats.record_stats()
|
||||
|
||||
def restart_threaded_server(self):
|
||||
def restart_threaded_server(self, lang):
|
||||
|
||||
# if not already running, let's just start
|
||||
if not self.check_running():
|
||||
self.run_threaded_server()
|
||||
self.run_threaded_server(lang)
|
||||
else:
|
||||
self.stop_threaded_server()
|
||||
time.sleep(2)
|
||||
self.run_threaded_server()
|
||||
self.run_threaded_server(lang)
|
||||
|
||||
def cleanup_server_object(self):
|
||||
self.PID = None
|
||||
self.start_time = None
|
||||
self.restart_count = 0
|
||||
self.is_crashed = False
|
||||
@ -210,35 +313,27 @@ class Server:
|
||||
self.process = None
|
||||
|
||||
def check_running(self):
|
||||
running = False
|
||||
# if process is None, we never tried to start
|
||||
if self.PID is None:
|
||||
return running
|
||||
|
||||
try:
|
||||
alive = self.process.isalive()
|
||||
if type(alive) is not bool:
|
||||
self.last_rc = alive
|
||||
running = False
|
||||
else:
|
||||
running = alive
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to find if server PID exists: {}".format(self.PID), exc_info=True)
|
||||
pass
|
||||
|
||||
return running
|
||||
if self.process is None:
|
||||
return False
|
||||
poll = self.process.poll()
|
||||
if poll is None:
|
||||
return True
|
||||
else:
|
||||
self.last_rc = poll
|
||||
return False
|
||||
|
||||
def send_command(self, command):
|
||||
|
||||
console.info("COMMAND TIME: {}".format(command))
|
||||
if not self.check_running() and command.lower() != 'start':
|
||||
logger.warning("Server not running, unable to send command \"{}\"".format(command))
|
||||
return False
|
||||
|
||||
logger.debug("Sending command {} to server via pexpect".format(command))
|
||||
logger.debug("Sending command {} to server".format(command))
|
||||
|
||||
# send it
|
||||
self.process.send(command + '\n')
|
||||
self.process.stdin.write("{}\n".format(command).encode('utf-8'))
|
||||
self.process.stdin.flush()
|
||||
|
||||
def crash_detected(self, name):
|
||||
|
||||
@ -260,18 +355,18 @@ class Server:
|
||||
"The server {} has crashed, crash detection is disabled and it will not be restarted".format(name))
|
||||
return False
|
||||
|
||||
def killpid(self, pid):
|
||||
logger.info("Terminating PID {} and all child processes".format(pid))
|
||||
process = psutil.Process(pid)
|
||||
def kill(self):
|
||||
logger.info("Terminating server {} and all child processes".format(self.server_id))
|
||||
process = psutil.Process(self.process.pid)
|
||||
|
||||
# for every sub process...
|
||||
for proc in process.children(recursive=True):
|
||||
# kill all the child processes - it sounds too wrong saying kill all the children (kevdagoat: lol!)
|
||||
logger.info("Sending SIGKILL to PID {}".format(proc.name))
|
||||
logger.info("Sending SIGKILL to server {}".format(proc.name))
|
||||
proc.kill()
|
||||
# kill the main process we are after
|
||||
logger.info('Sending SIGKILL to parent')
|
||||
process.kill()
|
||||
self.process.kill()
|
||||
|
||||
def get_start_time(self):
|
||||
if self.check_running():
|
||||
@ -279,6 +374,12 @@ class Server:
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_pid(self):
|
||||
if self.process is not None:
|
||||
return self.process.pid
|
||||
else:
|
||||
return None
|
||||
|
||||
def detect_crash(self):
|
||||
|
||||
logger.info("Detecting possible crash for server: {} ".format(self.name))
|
||||
@ -319,25 +420,143 @@ class Server:
|
||||
console.info("Removing old crash detection watcher thread")
|
||||
schedule.clear(self.name)
|
||||
|
||||
def is_backup_running(self):
|
||||
if self.is_backingup:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def backup_server(self):
|
||||
backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name=f"backup_{self.name}")
|
||||
logger.info("Starting Backup Thread for server {}.".format(self.settings['server_name']))
|
||||
if self.server_path == None:
|
||||
self.server_path = self.settings['path']
|
||||
logger.info("Backup Thread - Local server path not defined. Setting local server path variable.")
|
||||
#checks if the backup thread is currently alive for this server
|
||||
if not self.is_backingup:
|
||||
try:
|
||||
backup_thread.start()
|
||||
except Exception as ex:
|
||||
logger.error("Failed to start backup: {}".format(ex))
|
||||
return False
|
||||
else:
|
||||
logger.error("Backup is already being processed for server {}. Canceling backup request".format(self.settings['server_name']))
|
||||
return False
|
||||
logger.info("Backup Thread started for server {}.".format(self.settings['server_name']))
|
||||
|
||||
def a_backup_server(self):
|
||||
logger.info("Starting server {} (ID {}) backup".format(self.name, self.server_id))
|
||||
conf = db_helper.get_backup_config(self.server_id)
|
||||
self.is_backingup = True
|
||||
conf = management_helper.get_backup_config(self.server_id)
|
||||
try:
|
||||
backup_filename = "{}/{}.zip".format(conf['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
backup_filename = "{}/{}".format(self.settings['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
logger.info("Creating backup of server '{}' (ID#{}) at '{}'".format(self.settings['server_name'], self.server_id, backup_filename))
|
||||
helper.zip_directory(backup_filename, self.server_path)
|
||||
backup_list = self.list_backups()
|
||||
if len(self.list_backups()) > conf["max_backups"]:
|
||||
shutil.make_archive(backup_filename, 'zip', self.server_path)
|
||||
while len(self.list_backups()) > conf["max_backups"] and conf["max_backups"] > 0:
|
||||
backup_list = self.list_backups()
|
||||
oldfile = backup_list[0]
|
||||
logger.info("Removing old backup '{}'".format(oldfile))
|
||||
os.remove(oldfile)
|
||||
oldfile_path = "{}/{}".format(conf['backup_path'], oldfile['path'])
|
||||
logger.info("Removing old backup '{}'".format(oldfile['path']))
|
||||
os.remove(oldfile_path)
|
||||
self.is_backingup = False
|
||||
logger.info("Backup of server: {} completed".format(self.name))
|
||||
return
|
||||
except:
|
||||
logger.exception("Failed to create backup of server {} (ID {})".format(self.name, self.server_id))
|
||||
self.is_backingup = False
|
||||
return
|
||||
|
||||
def list_backups(self):
|
||||
conf = db_helper.get_backup_config(self.server_id)
|
||||
conf = management_helper.get_backup_config(self.server_id)
|
||||
if helper.check_path_exists(self.settings['backup_path']):
|
||||
files = helper.get_human_readable_files_sizes(helper.list_dir_by_date(self.settings['backup_path']))
|
||||
return [{"path": os.path.relpath(f['path'], start=conf['backup_path']), "size": f["size"]} for f in files]
|
||||
else:
|
||||
return []
|
||||
|
||||
def jar_update(self):
|
||||
servers_helper.set_update(self.server_id, True)
|
||||
update_thread = threading.Thread(target=self.a_jar_update, daemon=True, name=f"exe_update_{self.name}")
|
||||
update_thread.start()
|
||||
|
||||
def check_update(self):
|
||||
server_stats = servers_helper.get_server_stats_by_id(self.server_id)
|
||||
if server_stats['updating']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def a_jar_update(self):
|
||||
error = False
|
||||
wasStarted = "-1"
|
||||
self.backup_server()
|
||||
#checks if server is running. Calls shutdown if it is running.
|
||||
if self.check_running():
|
||||
wasStarted = True
|
||||
logger.info("Server with PID {} is running. Sending shutdown command".format(self.process.pid))
|
||||
self.stop_threaded_server()
|
||||
else:
|
||||
wasStarted = False
|
||||
if len(websocket_helper.clients) > 0:
|
||||
# There are clients
|
||||
self.check_update()
|
||||
message = '<a data-id="'+str(self.server_id)+'" class=""> UPDATING...</i></a>'
|
||||
websocket_helper.broadcast('update_button_status', {
|
||||
'isUpdating': self.check_update(),
|
||||
'server_id': self.server_id,
|
||||
'wasRunning': wasStarted,
|
||||
'string': message
|
||||
})
|
||||
backup_dir = os.path.join(self.settings['path'], 'crafty_executable_backups')
|
||||
#checks if backup directory already exists
|
||||
if os.path.isdir(backup_dir):
|
||||
backup_executable = os.path.join(backup_dir, 'old_server.jar')
|
||||
else:
|
||||
logger.info("Executable backup directory not found for Server: {}. Creating one.".format(self.name))
|
||||
os.mkdir(backup_dir)
|
||||
backup_executable = os.path.join(backup_dir, 'old_server.jar')
|
||||
|
||||
if os.path.isfile(backup_executable):
|
||||
#removes old backup
|
||||
logger.info("Old backup found for server: {}. Removing...".format(self.name))
|
||||
os.remove(backup_executable)
|
||||
logger.info("Old backup removed for server: {}.".format(self.name))
|
||||
else:
|
||||
logger.info("No old backups found for server: {}".format(self.name))
|
||||
|
||||
current_executable = os.path.join(self.settings['path'], self.settings['executable'])
|
||||
|
||||
#copies to backup dir
|
||||
helper.copy_files(current_executable, backup_executable)
|
||||
|
||||
#boolean returns true for false for success
|
||||
downloaded = helper.download_file(self.settings['executable_update_url'], current_executable)
|
||||
|
||||
while servers_helper.get_server_stats_by_id(self.server_id)['updating']:
|
||||
if downloaded and not self.is_backingup:
|
||||
print("Backup Status: " + str(self.is_backingup))
|
||||
logger.info("Executable updated successfully. Starting Server")
|
||||
|
||||
servers_helper.set_update(self.server_id, False)
|
||||
if len(websocket_helper.clients) > 0:
|
||||
# There are clients
|
||||
self.check_update()
|
||||
websocket_helper.broadcast('notification', "Executable update finished for " + self.name)
|
||||
time.sleep(3)
|
||||
websocket_helper.broadcast('update_button_status', {
|
||||
'isUpdating': self.check_update(),
|
||||
'server_id': self.server_id,
|
||||
'wasRunning': wasStarted
|
||||
})
|
||||
websocket_helper.broadcast('notification', "Executable update finished for "+self.name)
|
||||
|
||||
management_helper.add_to_audit_log_raw('Alert', '-1', self.server_id, "Executable update finished for "+self.name, self.settings['server_ip'])
|
||||
if wasStarted:
|
||||
self.start_server()
|
||||
elif not downloaded and not self.is_backingup:
|
||||
time.sleep(5)
|
||||
servers_helper.set_update(self.server_id, False)
|
||||
websocket_helper.broadcast('notification',
|
||||
"Executable update failed for " + self.name + ". Check log file for details.")
|
||||
logger.error("Executable download failed.")
|
||||
pass
|
||||
|
@ -5,6 +5,7 @@ import time
|
||||
import logging
|
||||
import threading
|
||||
import asyncio
|
||||
import shutil
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
@ -12,7 +13,8 @@ from app.classes.web.tornado import Webserver
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.shared.models import db_helper
|
||||
from app.classes.models.servers import servers_helper
|
||||
from app.classes.models.management import management_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -21,7 +23,7 @@ try:
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
scheduler_intervals = { 'seconds',
|
||||
@ -74,7 +76,7 @@ class TasksManager:
|
||||
time.sleep(5)
|
||||
|
||||
def reload_schedule_from_db(self):
|
||||
jobs = db_helper.get_schedules_enabled()
|
||||
jobs = management_helper.get_schedules_enabled()
|
||||
schedule.clear(tag='backup')
|
||||
schedule.clear(tag='db')
|
||||
for j in jobs:
|
||||
@ -83,7 +85,7 @@ class TasksManager:
|
||||
i=j.schedule_id, a=j.action, n=j.interval, t=j.interval_type, s=j.start_time))
|
||||
try:
|
||||
getattr(schedule.every(j.interval), j.interval_type).at(j.start_time).do(
|
||||
db_helper.send_command, 0, j.server_id, "127.27.23.89", j.action)
|
||||
self.controller.management.send_command, 0, j.server_id, "127.27.23.89", j.action)
|
||||
except schedule.ScheduleValueError as e:
|
||||
logger.critical("Scheduler value error occurred: {} on ID#{}".format(e, j.schedule_id))
|
||||
else:
|
||||
@ -92,25 +94,28 @@ class TasksManager:
|
||||
def command_watcher(self):
|
||||
while True:
|
||||
# select any commands waiting to be processed
|
||||
commands = db_helper.get_unactioned_commands()
|
||||
commands = management_helper.get_unactioned_commands()
|
||||
for c in commands:
|
||||
|
||||
svr = self.controller.get_server_obj(c['server_id']['server_id'])
|
||||
user_lang = c.get('user')['lang']
|
||||
command = c.get('command', None)
|
||||
|
||||
if command == 'start_server':
|
||||
svr.run_threaded_server()
|
||||
svr.run_threaded_server(user_lang)
|
||||
|
||||
elif command == 'stop_server':
|
||||
svr.stop_threaded_server()
|
||||
|
||||
elif command == "restart_server":
|
||||
svr.restart_threaded_server()
|
||||
svr.restart_threaded_server(user_lang)
|
||||
|
||||
elif command == "backup_server":
|
||||
svr.backup_server()
|
||||
|
||||
db_helper.mark_command_complete(c.get('command_id', None))
|
||||
elif command == "update_executable":
|
||||
svr.jar_update()
|
||||
management_helper.mark_command_complete(c.get('command_id', None))
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
@ -184,20 +189,20 @@ class TasksManager:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
host_stats = db_helper.get_latest_hosts_stats()
|
||||
host_stats = management_helper.get_latest_hosts_stats()
|
||||
|
||||
while True:
|
||||
|
||||
if host_stats.get('cpu_usage') != \
|
||||
db_helper.get_latest_hosts_stats().get('cpu_usage') or \
|
||||
management_helper.get_latest_hosts_stats().get('cpu_usage') or \
|
||||
host_stats.get('mem_percent') != \
|
||||
db_helper.get_latest_hosts_stats().get('mem_percent'):
|
||||
management_helper.get_latest_hosts_stats().get('mem_percent'):
|
||||
# Stats are different
|
||||
|
||||
host_stats = db_helper.get_latest_hosts_stats()
|
||||
host_stats = management_helper.get_latest_hosts_stats()
|
||||
if len(websocket_helper.clients) > 0:
|
||||
# There are clients
|
||||
websocket_helper.broadcast('update_host_stats', {
|
||||
websocket_helper.broadcast_page('/panel/dashboard', 'update_host_stats', {
|
||||
'cpu_usage': host_stats.get('cpu_usage'),
|
||||
'cpu_cores': host_stats.get('cpu_cores'),
|
||||
'cpu_cur_freq': host_stats.get('cpu_cur_freq'),
|
||||
@ -205,13 +210,9 @@ class TasksManager:
|
||||
'mem_percent': host_stats.get('mem_percent'),
|
||||
'mem_usage': host_stats.get('mem_usage')
|
||||
})
|
||||
time.sleep(4)
|
||||
else:
|
||||
# Stats are same
|
||||
time.sleep(8)
|
||||
time.sleep(4)
|
||||
|
||||
def log_watcher(self):
|
||||
console.debug('in log_watcher')
|
||||
helper.check_for_old_logs(db_helper)
|
||||
schedule.every(6).hours.do(lambda: helper.check_for_old_logs(db_helper)).tag('log-mgmt')
|
||||
self.controller.servers.check_for_old_logs()
|
||||
schedule.every(6).hours.do(lambda: self.controller.servers.check_for_old_logs()).tag('log-mgmt')
|
||||
|
||||
|
@ -1,57 +1,71 @@
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Translation():
|
||||
def __init__(self):
|
||||
self.translations_path = os.path.join(helper.root_dir, 'app', 'translations')
|
||||
def translate(self, page, word):
|
||||
translated_word = None
|
||||
lang = helper.get_setting('language')
|
||||
fallback_lang = 'en_EN'
|
||||
|
||||
translated_word = \
|
||||
self.translate_inner(page, word, lang) or \
|
||||
self.translate_inner(page, word, fallback_lang)
|
||||
|
||||
if translated_word:
|
||||
if isinstance(translated_word, dict): return json.dumps(translated_word)
|
||||
elif iter(translated_word) and not isinstance(translated_word, str): return '\n'.join(translated_word)
|
||||
return translated_word
|
||||
return 'Error while getting translation'
|
||||
def translate_inner(self, page, word, lang):
|
||||
lang_file = os.path.join(
|
||||
self.translations_path,
|
||||
lang + '.json'
|
||||
)
|
||||
try:
|
||||
with open(lang_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
try:
|
||||
translated_page = data[page]
|
||||
except KeyError:
|
||||
logger.error('Translation File Error: page {} does not exist for lang {}'.format(page, lang))
|
||||
console.error('Translation File Error: page {} does not exist for lang {}'.format(page, lang))
|
||||
return None
|
||||
|
||||
try:
|
||||
translated_word = translated_page[word]
|
||||
return translated_word
|
||||
except KeyError:
|
||||
logger.error('Translation File Error: word {} does not exist on page {} for lang {}'.format(word, page, lang))
|
||||
console.error('Translation File Error: word {} does not exist on page {} for lang {}'.format(word, page, lang))
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.critical('Translation File Error: Unable to read {} due to {}'.format(lang_file, e))
|
||||
console.critical('Translation File Error: Unable to read {} due to {}'.format(lang_file, e))
|
||||
return None
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Translation():
|
||||
def __init__(self):
|
||||
self.translations_path = os.path.join(helper.root_dir, 'app', 'translations')
|
||||
self.cached_translation = None
|
||||
self.cached_translation_lang = None
|
||||
self.lang_file_exists = []
|
||||
def translate(self, page, word, lang):
|
||||
translated_word = None
|
||||
fallback_lang = 'en_EN'
|
||||
|
||||
if lang not in self.lang_file_exists and \
|
||||
helper.check_file_exists(os.path.join(self.translations_path, str(lang) + '.json')):
|
||||
self.lang_file_exists.append(lang)
|
||||
|
||||
translated_word = self.translate_inner(page, word, lang) \
|
||||
if lang in self.lang_file_exists else self.translate_inner(page, word, fallback_lang)
|
||||
|
||||
if translated_word:
|
||||
if isinstance(translated_word, dict): return json.dumps(translated_word)
|
||||
elif iter(translated_word) and not isinstance(translated_word, str): return '\n'.join(translated_word)
|
||||
return translated_word
|
||||
return 'Error while getting translation'
|
||||
def translate_inner(self, page, word, lang):
|
||||
lang_file = os.path.join(
|
||||
self.translations_path,
|
||||
lang + '.json'
|
||||
)
|
||||
try:
|
||||
if not self.cached_translation:
|
||||
with open(lang_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self.cached_translation = data
|
||||
elif self.cached_translation_lang != lang:
|
||||
with open(lang_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self.cached_translation = data
|
||||
self.cached_translation_lang = lang
|
||||
else:
|
||||
data = self.cached_translation
|
||||
|
||||
try:
|
||||
translated_page = data[page]
|
||||
except KeyError:
|
||||
logger.error('Translation File Error: page {} does not exist for lang {}'.format(page, lang))
|
||||
console.error('Translation File Error: page {} does not exist for lang {}'.format(page, lang))
|
||||
return None
|
||||
|
||||
try:
|
||||
translated_word = translated_page[word]
|
||||
return translated_word
|
||||
except KeyError:
|
||||
logger.error('Translation File Error: word {} does not exist on page {} for lang {}'.format(word, page, lang))
|
||||
console.error('Translation File Error: word {} does not exist on page {} for lang {}'.format(word, page, lang))
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.critical('Translation File Error: Unable to read {} due to {}'.format(lang_file, e))
|
||||
console.critical('Translation File Error: Unable to read {} due to {}'.format(lang_file, e))
|
||||
return None
|
||||
|
||||
translation = Translation()
|
@ -1,16 +1,23 @@
|
||||
import json
|
||||
import logging
|
||||
import tempfile
|
||||
import threading
|
||||
from typing import Container
|
||||
import zipfile
|
||||
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import bleach
|
||||
import os
|
||||
import shutil
|
||||
import html
|
||||
import re
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Users, installer
|
||||
from app.classes.shared.main_models import Users, installer
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.models import db_helper
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.server import ServerOutBuf
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -47,28 +54,31 @@ class AjaxHandler(BaseHandler):
|
||||
if server_id is None:
|
||||
logger.warning("Server ID not found in server_log ajax call")
|
||||
self.redirect("/panel/error?error=Server ID Not Found")
|
||||
return False
|
||||
return
|
||||
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
server_data = db_helper.get_server_data_by_id(server_id)
|
||||
server_data = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not server_data:
|
||||
logger.warning("Server Data not found in server_log ajax call")
|
||||
self.redirect("/panel/error?error=Server ID Not Found")
|
||||
return
|
||||
|
||||
if not server_data['log_path']:
|
||||
logger.warning("Log path not found in server_log ajax call ({})".format(server_id))
|
||||
|
||||
if full_log:
|
||||
log_lines = helper.get_setting('max_log_lines')
|
||||
data = helper.tail_file(server_data['log_path'], log_lines)
|
||||
else:
|
||||
log_lines = helper.get_setting('virtual_terminal_lines')
|
||||
data = ServerOutBuf.lines.get(server_id, [])
|
||||
|
||||
data = helper.tail_file(server_data['log_path'], log_lines)
|
||||
|
||||
for d in data:
|
||||
try:
|
||||
line = helper.log_colors(d)
|
||||
d = re.sub('(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )', '', d)
|
||||
d = re.sub('[A-z]{2}\b\b', '', d)
|
||||
line = helper.log_colors(html.escape(d))
|
||||
self.write('{}<br />'.format(line))
|
||||
# self.write(d.encode("utf-8"))
|
||||
|
||||
@ -85,14 +95,14 @@ class AjaxHandler(BaseHandler):
|
||||
file_path = self.get_argument('file_path', None)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_file'): return False
|
||||
if not self.check_server_id(server_id, 'get_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in get_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in get_file ajax call ({})".format(file_path))
|
||||
return False
|
||||
return
|
||||
|
||||
|
||||
error = None
|
||||
@ -113,11 +123,11 @@ class AjaxHandler(BaseHandler):
|
||||
elif page == "get_tree":
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if not self.check_server_id(server_id, 'get_tree'): return False
|
||||
if not self.check_server_id(server_id, 'get_tree'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
self.write(db_helper.get_server_data_by_id(server_id)['path'] + '\n' +
|
||||
helper.generate_tree(db_helper.get_server_data_by_id(server_id)['path']))
|
||||
self.write(self.controller.servers.get_server_data_by_id(server_id)['path'] + '\n' +
|
||||
helper.generate_tree(self.controller.servers.get_server_data_by_id(server_id)['path']))
|
||||
self.finish()
|
||||
|
||||
@tornado.web.authenticated
|
||||
@ -144,21 +154,22 @@ class AjaxHandler(BaseHandler):
|
||||
if srv_obj.check_running():
|
||||
srv_obj.send_command(command)
|
||||
|
||||
self.controller.management.add_to_audit_log(user_data['user_id'], "Sent command to {} terminal: {}".format(self.controller.servers.get_server_friendly_name(server_id), command), server_id, self.get_remote_ip())
|
||||
|
||||
elif page == "create_file":
|
||||
file_parent = self.get_body_argument('file_parent', default=None, strip=True)
|
||||
file_name = self.get_body_argument('file_name', default=None, strip=True)
|
||||
file_path = os.path.join(file_parent, file_name)
|
||||
server_id = self.get_argument('id', None)
|
||||
print(server_id)
|
||||
|
||||
if not self.check_server_id(server_id, 'create_file'): return False
|
||||
if not self.check_server_id(server_id, 'create_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path) \
|
||||
or helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in create_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in create_file ajax call ({})".format(file_path))
|
||||
return False
|
||||
return
|
||||
|
||||
# Create the file by opening it
|
||||
with open(file_path, 'w') as file_object:
|
||||
@ -169,38 +180,56 @@ class AjaxHandler(BaseHandler):
|
||||
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
|
||||
dir_path = os.path.join(dir_parent, dir_name)
|
||||
server_id = self.get_argument('id', None)
|
||||
print(server_id)
|
||||
|
||||
if not self.check_server_id(server_id, 'create_dir'): return False
|
||||
if not self.check_server_id(server_id, 'create_dir'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], dir_path) \
|
||||
or helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
|
||||
console.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
|
||||
return False
|
||||
|
||||
return
|
||||
# Create the directory
|
||||
os.mkdir(dir_path)
|
||||
|
||||
elif page == "unzip_file":
|
||||
server_id = self.get_argument('id', None)
|
||||
path = self.get_argument('path', None)
|
||||
helper.unzipFile(path)
|
||||
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
|
||||
return
|
||||
|
||||
elif page == "kill":
|
||||
server_id = self.get_argument('id', None)
|
||||
svr = self.controller.get_server_obj(server_id)
|
||||
try:
|
||||
svr.kill()
|
||||
except Exception as e:
|
||||
logger.error("Could not find PID for requested termsig. Full error: {}".format(e))
|
||||
return
|
||||
|
||||
@tornado.web.authenticated
|
||||
def delete(self, page):
|
||||
if page == "del_file":
|
||||
file_path = self.get_body_argument('file_path', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
|
||||
if os.name == "nt":
|
||||
file_path = file_path.replace('/', "\\")
|
||||
|
||||
console.warning("delete {} for server {}".format(file_path, server_id))
|
||||
|
||||
if not self.check_server_id(server_id, 'del_file'): return False
|
||||
if not self.check_server_id(server_id, 'del_file'):
|
||||
return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = db_helper.get_server_data_by_id(server_id)
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not (helper.in_path(server_info['path'], file_path) \
|
||||
or helper.in_path(server_info['backup_path'], file_path)) \
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in del_file ajax call ({})".format(file_path))
|
||||
return False
|
||||
return
|
||||
|
||||
# Delete the file
|
||||
os.remove(file_path)
|
||||
@ -208,42 +237,50 @@ class AjaxHandler(BaseHandler):
|
||||
elif page == "del_dir":
|
||||
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
print(server_id)
|
||||
|
||||
console.warning("delete {} for server {}".format(file_path, server_id))
|
||||
console.warning("delete {} for server {}".format(dir_path, server_id))
|
||||
|
||||
if not self.check_server_id(server_id, 'del_dir'): return False
|
||||
if not self.check_server_id(server_id, 'del_dir'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
server_info = db_helper.get_server_data_by_id(server_id)
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
if not helper.in_path(server_info['path'], dir_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(dir_path)):
|
||||
logger.warning("Invalid path in del_file ajax call ({})".format(dir_path))
|
||||
console.warning("Invalid path in del_file ajax call ({})".format(dir_path))
|
||||
return False
|
||||
return
|
||||
|
||||
# Delete the directory
|
||||
# os.rmdir(dir_path) # Would only remove empty directories
|
||||
shutil.rmtree(dir_path) # Removes also when there are contents
|
||||
|
||||
elif page == "delete_server":
|
||||
server_id = self.get_argument('id', None)
|
||||
logger.info(
|
||||
"Removing server from panel for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
|
||||
self.controller.remove_server(server_id, False)
|
||||
|
||||
elif page == "delete_server_files":
|
||||
server_id = self.get_argument('id', None)
|
||||
logger.info(
|
||||
"Removing server and all associated files for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
|
||||
self.controller.remove_server(server_id, True)
|
||||
|
||||
@tornado.web.authenticated
|
||||
def put(self, page):
|
||||
if page == "save_file":
|
||||
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
|
||||
file_path = self.get_body_argument('file_path', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
print(file_contents)
|
||||
print(file_path)
|
||||
print(server_id)
|
||||
|
||||
if not self.check_server_id(server_id, 'save_file'): return False
|
||||
if not self.check_server_id(server_id, 'save_file'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], file_path)\
|
||||
or not helper.check_file_exists(os.path.abspath(file_path)):
|
||||
logger.warning("Invalid path in save_file ajax call ({})".format(file_path))
|
||||
console.warning("Invalid path in save_file ajax call ({})".format(file_path))
|
||||
return False
|
||||
return
|
||||
|
||||
# Open the file in write mode and store the content in file_object
|
||||
with open(file_path, 'w') as file_object:
|
||||
@ -253,29 +290,28 @@ class AjaxHandler(BaseHandler):
|
||||
item_path = self.get_body_argument('item_path', default=None, strip=True)
|
||||
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
|
||||
server_id = self.get_argument('id', None)
|
||||
print(server_id)
|
||||
|
||||
if not self.check_server_id(server_id, 'rename_item'): return False
|
||||
if not self.check_server_id(server_id, 'rename_item'): return
|
||||
else: server_id = bleach.clean(server_id)
|
||||
|
||||
if item_path is None or new_item_name is None:
|
||||
logger.warning("Invalid path(s) in rename_item ajax call")
|
||||
console.warning("Invalid path(s) in rename_item ajax call")
|
||||
return False
|
||||
return
|
||||
|
||||
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], item_path) \
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], item_path) \
|
||||
or not helper.check_path_exists(os.path.abspath(item_path)):
|
||||
logger.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
|
||||
console.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
|
||||
return False
|
||||
return
|
||||
|
||||
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
|
||||
|
||||
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], new_item_path) \
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], new_item_path) \
|
||||
or helper.check_path_exists(os.path.abspath(new_item_path)):
|
||||
logger.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
|
||||
console.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
|
||||
return False
|
||||
return
|
||||
|
||||
# RENAME
|
||||
os.rename(item_path, new_item_path)
|
||||
@ -283,13 +319,13 @@ class AjaxHandler(BaseHandler):
|
||||
if server_id is None:
|
||||
logger.warning("Server ID not defined in {} ajax call ({})".format(page_name, server_id))
|
||||
console.warning("Server ID not defined in {} ajax call ({})".format(page_name, server_id))
|
||||
return False
|
||||
return
|
||||
else:
|
||||
server_id = bleach.clean(server_id)
|
||||
|
||||
# does this server id exist?
|
||||
if not db_helper.server_id_exists(server_id):
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
logger.warning("Server ID not found in {} ajax call ({})".format(page_name, server_id))
|
||||
console.warning("Server ID not found in {} ajax call ({})".format(page_name, server_id))
|
||||
return False
|
||||
return
|
||||
return True
|
||||
|
@ -6,53 +6,62 @@ import tornado.escape
|
||||
import logging
|
||||
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.models import Users
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApiHandler(BaseHandler):
|
||||
|
||||
def return_response(self, data: dict):
|
||||
def return_response(self, status: int, data: dict):
|
||||
# Define a standardized response
|
||||
self.set_status(status)
|
||||
self.write(data)
|
||||
|
||||
def access_denied(self, user):
|
||||
log.info("User %s was denied access to API route", user)
|
||||
self.set_status(403)
|
||||
self.finish(self.return_response(403, {'error':'ACCESS_DENIED', 'info':'You were denied access to the requested resource'}))
|
||||
def access_denied(self, user, reason=''):
|
||||
if reason: reason = ' because ' + reason
|
||||
log.info("User %s from IP %s was denied access to the API route " + self.request.path + reason, user, self.get_remote_ip())
|
||||
self.finish(self.return_response(403, {
|
||||
'error':'ACCESS_DENIED',
|
||||
'info':'You were denied access to the requested resource'
|
||||
}))
|
||||
|
||||
def authenticate_user(self):
|
||||
def authenticate_user(self) -> bool:
|
||||
try:
|
||||
log.debug("Searching for specified token")
|
||||
# TODO: YEET THIS
|
||||
user_data = Users.get(api_token=self.get_argument('token'))
|
||||
user_data = self.controller.users.get_user_by_api_token(self.get_argument('token'))
|
||||
log.debug("Checking results")
|
||||
if user_data:
|
||||
# Login successful! Check perms
|
||||
log.info("User {} has authenticated to API".format(user_data.username))
|
||||
log.info("User {} has authenticated to API".format(user_data['username']))
|
||||
# TODO: Role check
|
||||
|
||||
return True # This is to set the "authenticated"
|
||||
else:
|
||||
logging.debug("Auth unsuccessful")
|
||||
return self.access_denied("unknown")
|
||||
except:
|
||||
log.warning("Traceback occurred when authenticating user to API. Most likely wrong token")
|
||||
return self.access_denied("unknown")
|
||||
pass
|
||||
self.access_denied("unknown", "the user provided an invalid token")
|
||||
return
|
||||
except Exception as e:
|
||||
log.warning("An error occured while authenticating an API user: %s", e)
|
||||
self.access_denied("unknown"), "an error occured while authenticating the user"
|
||||
return
|
||||
|
||||
|
||||
class ServersStats(ApiHandler):
|
||||
def get(self):
|
||||
"""Get details about all servers"""
|
||||
self.authenticate_user()
|
||||
authenticated = self.authenticate_user()
|
||||
if not authenticated: return
|
||||
# Get server stats
|
||||
# TODO Check perms
|
||||
self.finish(self.write({"servers": self.controller.stats.get_servers_stats()}))
|
||||
|
||||
|
||||
class NodeStats(ApiHandler):
|
||||
def get(self):
|
||||
"""Get stats for particular node"""
|
||||
self.authenticate_user()
|
||||
authenticated = self.authenticate_user()
|
||||
if not authenticated: return
|
||||
# Get node stats
|
||||
node_stats = self.controller.stats.get_node_stats()
|
||||
node_stats.pop("servers")
|
||||
|
@ -7,6 +7,8 @@ from typing import (
|
||||
Optional
|
||||
)
|
||||
|
||||
from app.classes.shared.main_controller import Controller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -15,7 +17,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
nobleach = {bool, type(None)}
|
||||
redactables = ("pass", "api")
|
||||
|
||||
def initialize(self, controller=None, tasks_manager=None, translator=None):
|
||||
def initialize(self, controller : Controller = None, tasks_manager=None, translator=None):
|
||||
self.controller = controller
|
||||
self.tasks_manager = tasks_manager
|
||||
self.translator = translator
|
||||
|
57
app/classes/web/http_handler.py
Normal file
@ -0,0 +1,57 @@
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import requests
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import Users, fn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class HTTPHandler(BaseHandler):
|
||||
def get(self):
|
||||
url = str(self.request.host)
|
||||
port = 443
|
||||
url_list = url.split(":")
|
||||
if url_list[0] != "":
|
||||
url = 'https://' + url_list[0]
|
||||
else:
|
||||
url = 'https://' + url
|
||||
db_port = helper.get_setting('https_port')
|
||||
try:
|
||||
resp = requests.get(url + ":" + str(port))
|
||||
resp.raise_for_status()
|
||||
except Exception as err:
|
||||
port = db_port
|
||||
self.redirect(url+":"+str(port))
|
||||
|
||||
|
||||
class HTTPHandlerPage(BaseHandler):
|
||||
def get(self, page):
|
||||
url = str(self.request.host)
|
||||
port = 443
|
||||
url_list = url.split(":")
|
||||
if url_list[0] != "":
|
||||
url = 'https://' + url_list[0]
|
||||
else:
|
||||
url = 'https://' + url
|
||||
db_port = helper.get_setting('https_port')
|
||||
try:
|
||||
resp = requests.get(url + ":" + str(port))
|
||||
resp.raise_for_status()
|
||||
except Exception as err:
|
||||
port = db_port
|
||||
self.redirect(url+":"+str(port))
|
49
app/classes/web/http_handler_page.py
Normal file
@ -0,0 +1,49 @@
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import requests
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import Users, fn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class HTTPHandlerPage(BaseHandler):
|
||||
def get(self, page):
|
||||
url = self.request.full_url
|
||||
port = 443
|
||||
print(url)
|
||||
if url[len(url)-1] == '/':
|
||||
url = url.strip(url[len(url)-1])
|
||||
url_list = url.split('/')
|
||||
print(url_list)
|
||||
if url_list[0] != "":
|
||||
primary_url = url_list[0] + ":"+str(port)+"/"
|
||||
backup_url = url_list[0] + ":" +str(helper.get_setting["https_port"]) +"/"
|
||||
for i in range(len(url_list)-1):
|
||||
primary_url += url_list[i+1]
|
||||
backup_url += url_list[i+1]
|
||||
else:
|
||||
primary_url = url + str(port)
|
||||
backup_url = url + str(helper.get_setting['https_port'])
|
||||
|
||||
try:
|
||||
resp = requests.get(primary_url)
|
||||
resp.raise_for_status()
|
||||
url = primary_url
|
||||
except Exception as err:
|
||||
url = backup_url
|
||||
self.redirect('https://'+url+':'+ str(port))
|
@ -1,3 +1,4 @@
|
||||
from app.classes.shared.translation import Translation
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
@ -7,10 +8,17 @@ import time
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from tornado import iostream
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Users, installer
|
||||
from app.classes.shared.main_models import Users, installer
|
||||
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.models import db_helper, Servers
|
||||
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -29,18 +37,20 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
exec_user_id = exec_user_data['user_id']
|
||||
exec_user = db_helper.get_user(exec_user_id)
|
||||
|
||||
exec_user = self.controller.users.get_user_by_id(exec_user_id)
|
||||
|
||||
exec_user_role = set()
|
||||
if exec_user['superuser'] == 1:
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
exec_user_role.add("Super User")
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
|
||||
else:
|
||||
defined_servers = self.controller.list_authorized_servers(exec_user_id)
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user_id)
|
||||
logger.debug(exec_user['roles'])
|
||||
for r in exec_user['roles']:
|
||||
role = db_helper.get_role(r)
|
||||
role = self.controller.roles.get_role(r)
|
||||
exec_user_role.add(role['role_name'])
|
||||
defined_servers = self.controller.servers.get_authorized_servers(exec_user_id)
|
||||
|
||||
page_data = {
|
||||
# todo: make this actually pull and compare version data
|
||||
@ -48,23 +58,25 @@ class PanelHandler(BaseHandler):
|
||||
'version_data': helper.get_version_string(),
|
||||
'user_data': exec_user_data,
|
||||
'user_role' : exec_user_role,
|
||||
'user_crafty_permissions' : exec_user_crafty_permissions,
|
||||
'crafty_permissions': {
|
||||
'Server_Creation': Enum_Permissions_Crafty.Server_Creation,
|
||||
'User_Config': Enum_Permissions_Crafty.User_Config,
|
||||
'Roles_Config': Enum_Permissions_Crafty.Roles_Config,
|
||||
},
|
||||
'server_stats': {
|
||||
'total': len(defined_servers),
|
||||
'running': len(self.controller.list_running_servers()),
|
||||
'stopped': (len(self.controller.list_defined_servers()) - len(self.controller.list_running_servers()))
|
||||
},
|
||||
'menu_servers': defined_servers,
|
||||
'hosts_data': db_helper.get_latest_hosts_stats(),
|
||||
'hosts_data': self.controller.management.get_latest_hosts_stats(),
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True),
|
||||
'error': error,
|
||||
'time': formatted_time
|
||||
}
|
||||
|
||||
# if no servers defined, let's go to the build server area
|
||||
if page_data['server_stats']['total'] == 0 and page != "error":
|
||||
self.set_status(301)
|
||||
self.redirect("/server/step1")
|
||||
return
|
||||
page_data['lang'] = self.controller.users.get_user_lang_by_id(exec_user_id)
|
||||
page_data['super_user'] = exec_user['superuser']
|
||||
|
||||
if page == 'unauthorized':
|
||||
template = "panel/denied.html"
|
||||
@ -75,9 +87,11 @@ class PanelHandler(BaseHandler):
|
||||
elif page == 'credits':
|
||||
with open(helper.credits_cache) as republic_credits_will_do:
|
||||
credits = json.load(republic_credits_will_do)
|
||||
page_data["patreons"] = credits["patreons"]
|
||||
timestamp = credits["lastUpdate"] / 1000.0
|
||||
page_data["patrons"] = credits["patrons"]
|
||||
page_data["staff"] = credits["staff"]
|
||||
page_data["translations"] = credits["translations"]
|
||||
page_data["lastUpdate"] = str(datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S'))
|
||||
template = "panel/credits.html"
|
||||
|
||||
elif page == 'contribute':
|
||||
@ -96,7 +110,7 @@ class PanelHandler(BaseHandler):
|
||||
server_data = self.controller.get_server_data(server_id)
|
||||
server_name = server_data['server_name']
|
||||
|
||||
db_helper.add_to_audit_log(exec_user_data['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
|
||||
"Deleted server {} named {}".format(server_id, server_name),
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
@ -107,19 +121,39 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
elif page == 'dashboard':
|
||||
if exec_user['superuser'] == 1:
|
||||
page_data['servers'] = db_helper.get_all_servers_stats()
|
||||
page_data['servers'] = self.controller.servers.get_all_servers_stats()
|
||||
for data in page_data['servers']:
|
||||
try:
|
||||
data['stats']['waiting_start'] = self.controller.servers.get_waiting_start(int(data['stats']['server_id']['server_id']))
|
||||
except:
|
||||
data['stats']['waiting_start'] = False
|
||||
else:
|
||||
#page_data['servers'] = db_helper.get_authorized_servers_stats(exec_user_id)
|
||||
ras = db_helper.get_authorized_servers_stats_from_roles(exec_user_id)
|
||||
logger.debug("ASFR: {}".format(ras))
|
||||
page_data['servers'] = ras
|
||||
user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id)
|
||||
logger.debug("ASFR: {}".format(user_auth))
|
||||
page_data['servers'] = user_auth
|
||||
page_data['server_stats']['running'] = 0
|
||||
page_data['server_stats']['stopped'] = 0
|
||||
for data in page_data['servers']:
|
||||
if data['stats']['running']:
|
||||
page_data['server_stats']['running'] += 1
|
||||
else:
|
||||
page_data['server_stats']['stopped'] += 1
|
||||
try:
|
||||
page_data['stats']['waiting_start'] = self.controller.servers.get_waiting_start(int(data['stats']['server_id']['server_id']))
|
||||
except:
|
||||
data['stats']['waiting_start'] = False
|
||||
|
||||
total_players = 0
|
||||
for server in page_data['servers']:
|
||||
total_players += len(self.controller.stats.get_server_players(server['server_data']['server_id']))
|
||||
page_data['num_players'] = total_players
|
||||
|
||||
for s in page_data['servers']:
|
||||
try:
|
||||
data = json.loads(s['int_ping_results'])
|
||||
s['int_ping_results'] = data
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error("Failed server data for page with error: {} ".format(e))
|
||||
|
||||
template = "panel/dashboard.html"
|
||||
|
||||
@ -132,15 +166,15 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not db_helper.server_id_exists(server_id):
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
if exec_user['superuser'] != 1:
|
||||
#if not db_helper.server_id_authorized(server_id, exec_user_id):
|
||||
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return False
|
||||
if not self.controller.servers.server_id_authorized(server_id, exec_user_id):
|
||||
if not self.controller.servers.server_id_authorized(int(server_id), exec_user_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return False
|
||||
|
||||
valid_subpages = ['term', 'logs', 'backup', 'config', 'files', 'admin_controls']
|
||||
|
||||
@ -151,15 +185,34 @@ class PanelHandler(BaseHandler):
|
||||
|
||||
server = self.controller.get_server_obj(server_id)
|
||||
# server_data isn't needed since the server_stats also pulls server data
|
||||
page_data['server_data'] = db_helper.get_server_data_by_id(server_id)
|
||||
page_data['server_stats'] = db_helper.get_server_stats_by_id(server_id)
|
||||
page_data['server_data'] = self.controller.servers.get_server_data_by_id(server_id)
|
||||
page_data['server_stats'] = self.controller.servers.get_server_stats_by_id(server_id)
|
||||
try:
|
||||
page_data['waiting_start'] = self.controller.servers.get_waiting_start(server_id)
|
||||
except:
|
||||
page_data['waiting_start'] = False
|
||||
page_data['get_players'] = lambda: self.controller.stats.get_server_players(server_id)
|
||||
page_data['active_link'] = subpage
|
||||
page_data['permissions'] = {
|
||||
'Commands': Enum_Permissions_Server.Commands,
|
||||
'Terminal': Enum_Permissions_Server.Terminal,
|
||||
'Logs': Enum_Permissions_Server.Logs,
|
||||
'Schedule': Enum_Permissions_Server.Schedule,
|
||||
'Backup': Enum_Permissions_Server.Backup,
|
||||
'Files': Enum_Permissions_Server.Files,
|
||||
'Config': Enum_Permissions_Server.Config,
|
||||
'Players': Enum_Permissions_Server.Players,
|
||||
}
|
||||
page_data['user_permissions'] = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
|
||||
|
||||
if subpage == "backup":
|
||||
page_data['backup_config'] = db_helper.get_backup_config(server_id)
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
page_data['backup_config'] = self.controller.management.get_backup_config(server_id)
|
||||
page_data['backup_list'] = server.list_backups()
|
||||
page_data['backup_path'] = server_info["backup_path"].replace('\\', '/')
|
||||
|
||||
def get_banned_players_html():
|
||||
banned_players = helper.get_banned_players(server_id, db_helper)
|
||||
banned_players = self.controller.servers.get_banned_players(server_id)
|
||||
if banned_players is None:
|
||||
return """
|
||||
<li class="playerItem banned">
|
||||
@ -175,7 +228,7 @@ class PanelHandler(BaseHandler):
|
||||
<button onclick="send_command_to_server('pardon {}')" type="button" class="btn btn-danger">Unban</button>
|
||||
</li>
|
||||
""".format(player['name'], player['source'], player['reason'], player['name'])
|
||||
|
||||
|
||||
return html
|
||||
if subpage == "admin_controls":
|
||||
page_data['banned_players'] = get_banned_players_html()
|
||||
@ -192,17 +245,17 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not db_helper.server_id_exists(server_id):
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
if exec_user['superuser'] != 1:
|
||||
#if not db_helper.server_id_authorized(server_id, exec_user_id):
|
||||
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
|
||||
#if not self.controller.servers.server_id_authorized(server_id, exec_user_id):
|
||||
if not self.controller.servers.server_id_authorized(int(server_id), exec_user_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return False
|
||||
return
|
||||
|
||||
server_info = db_helper.get_server_data_by_id(server_id)
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
backup_file = os.path.abspath(os.path.join(server_info["backup_path"], file))
|
||||
if not helper.in_path(server_info["backup_path"], backup_file) \
|
||||
or not os.path.isfile(backup_file):
|
||||
@ -226,9 +279,9 @@ class PanelHandler(BaseHandler):
|
||||
# so break the loop
|
||||
break
|
||||
finally:
|
||||
# deleting the chunk is very important because
|
||||
# if many clients are downloading files at the
|
||||
# same time, the chunks in memory will keep
|
||||
# deleting the chunk is very important because
|
||||
# if many clients are downloading files at the
|
||||
# same time, the chunks in memory will keep
|
||||
# increasing and will eat up the RAM
|
||||
del chunk
|
||||
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
|
||||
@ -241,22 +294,60 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not db_helper.server_id_exists(server_id):
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
if exec_user['superuser'] != 1:
|
||||
#if not db_helper.server_id_authorized(server_id, exec_user_id):
|
||||
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
|
||||
#if not self.controller.servers.server_id_authorized(server_id, exec_user_id):
|
||||
if not self.controller.servers.server_id_authorized(int(server_id), exec_user_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return False
|
||||
return
|
||||
|
||||
server = self.controller.get_server_obj(server_id).backup_server()
|
||||
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
|
||||
|
||||
elif page == 'panel_config':
|
||||
page_data['users'] = db_helper.get_all_users()
|
||||
page_data['roles'] = db_helper.get_all_roles()
|
||||
auth_servers = {}
|
||||
auth_role_servers = {}
|
||||
users_list = []
|
||||
role_users = {}
|
||||
roles = self.controller.roles.get_all_roles()
|
||||
user_roles = {}
|
||||
for user in self.controller.users.get_all_users():
|
||||
user_roles_list = self.controller.users.get_user_roles_names(user.user_id)
|
||||
user_servers = self.controller.servers.get_authorized_servers(user.user_id)
|
||||
servers = []
|
||||
for server in user_servers:
|
||||
if server['server_name'] not in servers:
|
||||
servers.append(server['server_name'])
|
||||
new_item = {user.user_id: servers}
|
||||
auth_servers.update(new_item)
|
||||
data = {user.user_id: user_roles_list}
|
||||
user_roles.update(data)
|
||||
for role in roles:
|
||||
role_servers = []
|
||||
role = self.controller.roles.get_role_with_servers(role.role_id)
|
||||
for serv_id in role['servers']:
|
||||
role_servers.append(self.controller.servers.get_server_data_by_id(serv_id)['server_name'])
|
||||
data = {role['role_id']: role_servers}
|
||||
auth_role_servers.update(data)
|
||||
|
||||
|
||||
page_data['auth-servers'] = auth_servers
|
||||
page_data['role-servers'] = auth_role_servers
|
||||
page_data['user-roles'] = user_roles
|
||||
|
||||
if exec_user['superuser'] == 1:
|
||||
super_auth_servers = []
|
||||
super_auth_servers.append("Access To All Servers")
|
||||
page_data['users'] = self.controller.users.get_all_users()
|
||||
page_data['roles'] = self.controller.roles.get_all_roles()
|
||||
page_data['auth-servers'][exec_user['user_id']] = super_auth_servers
|
||||
else:
|
||||
page_data['users'] = self.controller.users.user_query(exec_user['user_id'])
|
||||
page_data['roles'] = self.controller.users.user_role_query(exec_user['user_id'])
|
||||
|
||||
for user in page_data['users']:
|
||||
if user.user_id != exec_user['user_id']:
|
||||
user.api_token = "********"
|
||||
@ -276,29 +367,63 @@ class PanelHandler(BaseHandler):
|
||||
page_data['user']['last_update'] = "N/A"
|
||||
page_data['user']['roles'] = set()
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a user editor")
|
||||
return
|
||||
|
||||
page_data['roles_all'] = db_helper.get_all_roles()
|
||||
page_data['roles_all'] = self.controller.roles.get_all_roles()
|
||||
page_data['servers'] = []
|
||||
page_data['servers_all'] = self.controller.list_defined_servers()
|
||||
page_data['role-servers'] = []
|
||||
page_data['permissions_all'] = self.controller.crafty_perms.list_defined_crafty_permissions()
|
||||
page_data['permissions_list'] = set()
|
||||
page_data['quantity_server'] = self.controller.crafty_perms.list_all_crafty_permissions_quantity_limits()
|
||||
page_data['languages'] = []
|
||||
page_data['languages'].append(self.controller.users.get_user_lang_by_id(exec_user_id))
|
||||
for file in os.listdir(os.path.join(helper.root_dir, 'app', 'translations')):
|
||||
if file.endswith('.json'):
|
||||
if file != str(page_data['languages'][0] + '.json'):
|
||||
page_data['languages'].append(file.split('.')[0])
|
||||
|
||||
template = "panel/panel_edit_user.html"
|
||||
|
||||
elif page == "edit_user":
|
||||
page_data['new_user'] = False
|
||||
user_id = self.get_argument('id', None)
|
||||
page_data['user'] = db_helper.get_user(user_id)
|
||||
page_data['servers'] = db_helper.get_authorized_servers_stats_from_roles(user_id)
|
||||
page_data['roles_all'] = db_helper.get_all_roles()
|
||||
role_servers = self.controller.servers.get_authorized_servers(user_id)
|
||||
page_role_servers = []
|
||||
servers = set()
|
||||
for server in role_servers:
|
||||
page_role_servers.append(server['server_id'])
|
||||
page_data['new_user'] = False
|
||||
page_data['user'] = self.controller.users.get_user_by_id(user_id)
|
||||
page_data['servers'] = servers
|
||||
page_data['role-servers'] = page_role_servers
|
||||
page_data['roles_all'] = self.controller.roles.get_all_roles()
|
||||
page_data['servers_all'] = self.controller.list_defined_servers()
|
||||
page_data['permissions_all'] = self.controller.crafty_perms.list_defined_crafty_permissions()
|
||||
page_data['permissions_list'] = self.controller.crafty_perms.get_crafty_permissions_list(user_id)
|
||||
page_data['quantity_server'] = self.controller.crafty_perms.list_crafty_permissions_quantity_limits(user_id)
|
||||
page_data['languages'] = []
|
||||
page_data['languages'].append(self.controller.users.get_user_lang_by_id(user_id))
|
||||
for file in os.listdir(os.path.join(helper.root_dir, 'app', 'translations')):
|
||||
if file.endswith('.json'):
|
||||
if file != str(page_data['languages'][0] + '.json'):
|
||||
page_data['languages'].append(file.split('.')[0])
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
return
|
||||
elif user_id is None:
|
||||
if user_id is None:
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
return
|
||||
elif Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
if str(user_id) != str(exec_user_id):
|
||||
print("USER ID ", user_id)
|
||||
print("EXEC ID ", exec_user_id)
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a user editor")
|
||||
return
|
||||
|
||||
page_data['servers'] = []
|
||||
page_data['role-servers'] = []
|
||||
page_data['roles_all'] = []
|
||||
page_data['servers_all'] = []
|
||||
|
||||
if exec_user['user_id'] != page_data['user']['user_id']:
|
||||
page_data['user']['api_token'] = "********"
|
||||
@ -315,7 +440,7 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this user id exist?
|
||||
target_user = db_helper.get_user(user_id)
|
||||
target_user = self.controller.users.get_user_by_id(user_id)
|
||||
if not target_user:
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
return
|
||||
@ -323,15 +448,21 @@ class PanelHandler(BaseHandler):
|
||||
self.redirect("/panel/error?error=Cannot remove a superuser")
|
||||
return
|
||||
|
||||
db_helper.remove_user(user_id)
|
||||
self.controller.users.remove_user(user_id)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Removed user {} (UID:{})".format(target_user['username'], user_id),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
self.redirect("/panel/panel_config")
|
||||
|
||||
elif page == "add_role":
|
||||
user_roles = {}
|
||||
for user in self.controller.users.get_all_users():
|
||||
user_roles_list = self.controller.users.get_user_roles_names(user.user_id)
|
||||
user_servers = self.controller.servers.get_authorized_servers(user.user_id)
|
||||
data = {user.user_id: user_roles_list}
|
||||
user_roles.update(data)
|
||||
page_data['new_role'] = True
|
||||
page_data['role'] = {}
|
||||
page_data['role']['role_name'] = ""
|
||||
@ -339,22 +470,38 @@ class PanelHandler(BaseHandler):
|
||||
page_data['role']['created'] = "N/A"
|
||||
page_data['role']['last_update'] = "N/A"
|
||||
page_data['role']['servers'] = set()
|
||||
page_data['user-roles'] = user_roles
|
||||
page_data['users'] = self.controller.users.get_all_users()
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if Enum_Permissions_Crafty.Roles_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a role editor")
|
||||
return
|
||||
|
||||
page_data['servers_all'] = self.controller.list_defined_servers()
|
||||
page_data['permissions_all'] = self.controller.server_perms.list_defined_permissions()
|
||||
page_data['permissions_list'] = set()
|
||||
template = "panel/panel_edit_role.html"
|
||||
|
||||
elif page == "edit_role":
|
||||
auth_servers = {}
|
||||
|
||||
user_roles = {}
|
||||
for user in self.controller.users.get_all_users():
|
||||
user_roles_list = self.controller.users.get_user_roles_names(user.user_id)
|
||||
user_servers = self.controller.servers.get_authorized_servers(user.user_id)
|
||||
data = {user.user_id: user_roles_list}
|
||||
user_roles.update(data)
|
||||
page_data['new_role'] = False
|
||||
role_id = self.get_argument('id', None)
|
||||
page_data['role'] = db_helper.get_role(role_id)
|
||||
page_data['role'] = self.controller.roles.get_role_with_servers(role_id)
|
||||
page_data['servers_all'] = self.controller.list_defined_servers()
|
||||
page_data['permissions_all'] = self.controller.server_perms.list_defined_permissions()
|
||||
page_data['permissions_list'] = self.controller.server_perms.get_role_permissions(role_id)
|
||||
page_data['user-roles'] = user_roles
|
||||
page_data['users'] = self.controller.users.get_all_users()
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if Enum_Permissions_Crafty.Roles_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a role editor")
|
||||
return
|
||||
elif role_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Role ID")
|
||||
@ -373,24 +520,75 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this user id exist?
|
||||
target_role = db_helper.get_role(role_id)
|
||||
target_role = self.controller.roles.get_role(role_id)
|
||||
if not target_role:
|
||||
self.redirect("/panel/error?error=Invalid Role ID")
|
||||
return
|
||||
|
||||
db_helper.remove_role(role_id)
|
||||
self.controller.roles.remove_role(role_id)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Removed role {} (RID:{})".format(target_role['role_name'], role_id),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
self.redirect("/panel/panel_config")
|
||||
|
||||
elif page == "activity_logs":
|
||||
page_data['audit_logs'] = db_helper.get_actity_log()
|
||||
page_data['audit_logs'] = self.controller.management.get_actity_log()
|
||||
|
||||
template = "panel/activity_logs.html"
|
||||
|
||||
elif page == 'download_file':
|
||||
server_id = self.get_argument('id', None)
|
||||
file = self.get_argument('path', "")
|
||||
name = self.get_argument('name', "")
|
||||
|
||||
if server_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
if exec_user['superuser'] != 1:
|
||||
if not self.controller.servers.server_id_authorized(int(server_id), exec_user_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
server_info = self.controller.servers.get_server_data_by_id(server_id)
|
||||
|
||||
if not helper.in_path(server_info["path"], file) \
|
||||
or not os.path.isfile(file):
|
||||
self.redirect("/panel/error?error=Invalid path detected")
|
||||
return
|
||||
|
||||
self.set_header('Content-Type', 'application/octet-stream')
|
||||
self.set_header('Content-Disposition', 'attachment; filename=' + name)
|
||||
chunk_size = 1024 * 1024 * 4 # 4 MiB
|
||||
|
||||
with open(file, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
try:
|
||||
self.write(chunk) # write the chunk to response
|
||||
self.flush() # send the chunk to client
|
||||
except iostream.StreamClosedError:
|
||||
# this means the client has closed the connection
|
||||
# so break the loop
|
||||
break
|
||||
finally:
|
||||
# deleting the chunk is very important because
|
||||
# if many clients are downloading files at the
|
||||
# same time, the chunks in memory will keep
|
||||
# increasing and will eat up the RAM
|
||||
del chunk
|
||||
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
|
||||
|
||||
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
@ -403,16 +601,18 @@ class PanelHandler(BaseHandler):
|
||||
def post(self, page):
|
||||
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
exec_user_id = exec_user_data['user_id']
|
||||
exec_user = db_helper.get_user(exec_user_id)
|
||||
exec_user = self.controller.users.get_user_by_id(exec_user_id)
|
||||
|
||||
exec_user_role = set()
|
||||
if exec_user['superuser'] == 1:
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
exec_user_role.add("Super User")
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
|
||||
else:
|
||||
defined_servers = self.controller.list_authorized_servers(exec_user_id)
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user_id)
|
||||
defined_servers = self.controller.servers.get_authorized_servers(exec_user_id)
|
||||
for r in exec_user['roles']:
|
||||
role = db_helper.get_role(r)
|
||||
role = self.controller.roles.get_role(r)
|
||||
exec_user_role.add(role['role_name'])
|
||||
|
||||
if page == 'server_detail':
|
||||
@ -426,23 +626,26 @@ class PanelHandler(BaseHandler):
|
||||
auto_start_delay = self.get_argument('auto_start_delay', '10')
|
||||
server_ip = self.get_argument('server_ip', None)
|
||||
server_port = self.get_argument('server_port', None)
|
||||
executable_update_url = self.get_argument('executable_update_url', None)
|
||||
auto_start = int(float(self.get_argument('auto_start', '0')))
|
||||
crash_detection = int(float(self.get_argument('crash_detection', '0')))
|
||||
logs_delete_after = int(float(self.get_argument('logs_delete_after', '0')))
|
||||
subpage = self.get_argument('subpage', None)
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
return
|
||||
if not self.controller.servers.server_id_authorized(server_id, exec_user_id):
|
||||
self.redirect("/panel/error?error=Unauthorized access: invalid server id")
|
||||
return
|
||||
elif server_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not db_helper.server_id_exists(server_id):
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
#TODO use controller method
|
||||
Servers.update({
|
||||
Servers.server_name: server_name,
|
||||
Servers.path: server_path,
|
||||
@ -454,13 +657,14 @@ class PanelHandler(BaseHandler):
|
||||
Servers.server_ip: server_ip,
|
||||
Servers.server_port: server_port,
|
||||
Servers.auto_start: auto_start,
|
||||
Servers.executable_update_url: executable_update_url,
|
||||
Servers.crash_detection: crash_detection,
|
||||
Servers.logs_delete_after: logs_delete_after,
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
|
||||
self.controller.refresh_server_settings(server_id)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited server {} named {}".format(server_id, server_name),
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
@ -472,7 +676,10 @@ class PanelHandler(BaseHandler):
|
||||
server_id = self.get_argument('id', None)
|
||||
backup_path = bleach.clean(self.get_argument('backup_path', None))
|
||||
max_backups = bleach.clean(self.get_argument('max_backups', None))
|
||||
enabled = int(float(bleach.clean(self.get_argument('auto_enabled'), '0')))
|
||||
try:
|
||||
enabled = int(float(bleach.clean(self.get_argument('auto_enabled'), '0')))
|
||||
except Exception as e:
|
||||
enabled = '0'
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
@ -482,17 +689,23 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this server id exist?
|
||||
if not db_helper.server_id_exists(server_id):
|
||||
if not self.controller.servers.server_id_exists(server_id):
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
return
|
||||
|
||||
if backup_path is not None:
|
||||
Servers.update({
|
||||
Servers.backup_path: backup_path
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
db_helper.set_backup_config(server_id, max_backups=max_backups)
|
||||
if enabled == '0':
|
||||
Servers.update({
|
||||
Servers.backup_path: backup_path
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
self.controller.management.set_backup_config(server_id, max_backups=max_backups, auto_enabled=False)
|
||||
else:
|
||||
Servers.update({
|
||||
Servers.backup_path: backup_path
|
||||
}).where(Servers.server_id == server_id).execute()
|
||||
self.controller.management.set_backup_config(server_id, max_backups=max_backups, auto_enabled=True)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited server {}: updated backups".format(server_id),
|
||||
server_id,
|
||||
self.get_remote_ip())
|
||||
@ -506,9 +719,26 @@ class PanelHandler(BaseHandler):
|
||||
password1 = bleach.clean(self.get_argument('password1', None))
|
||||
enabled = int(float(self.get_argument('enabled', '0')))
|
||||
regen_api = int(float(self.get_argument('regen_api', '0')))
|
||||
lang = bleach.clean(self.get_argument('language'), 'en_EN')
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
if str(user_id) != str(exec_user_id):
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a user editor")
|
||||
return
|
||||
|
||||
user_data = {
|
||||
"username": username,
|
||||
"password": password0,
|
||||
"lang": lang,
|
||||
}
|
||||
self.controller.users.update_user(user_id, user_data=user_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited user {} (UID:{}) password".format(username,
|
||||
user_id),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
self.redirect("/panel/panel_config")
|
||||
return
|
||||
elif username is None or username == "":
|
||||
self.redirect("/panel/error?error=Invalid username")
|
||||
@ -518,7 +748,7 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this user id exist?
|
||||
if not db_helper.user_id_exists(user_id):
|
||||
if not self.controller.users.user_id_exists(user_id):
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
return
|
||||
|
||||
@ -527,7 +757,7 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
|
||||
roles = set()
|
||||
for role in db_helper.get_all_roles():
|
||||
for role in self.controller.roles.get_all_roles():
|
||||
argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('role_{}_membership'.format(role.role_id), '0')
|
||||
@ -536,17 +766,43 @@ class PanelHandler(BaseHandler):
|
||||
if argument:
|
||||
roles.add(role.role_id)
|
||||
|
||||
permissions_mask = "000"
|
||||
server_quantity = {}
|
||||
for permission in self.controller.crafty_perms.list_defined_crafty_permissions():
|
||||
argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('permission_{}'.format(permission.name), '0')
|
||||
)
|
||||
))
|
||||
if argument:
|
||||
permissions_mask = self.controller.crafty_perms.set_permission(permissions_mask, permission, argument)
|
||||
|
||||
q_argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('quantity_{}'.format(permission.name), '0')
|
||||
)
|
||||
))
|
||||
if q_argument:
|
||||
server_quantity[permission.name] = q_argument
|
||||
else:
|
||||
server_quantity[permission.name] = 0
|
||||
|
||||
user_data = {
|
||||
"username": username,
|
||||
"password": password0,
|
||||
"enabled": enabled,
|
||||
"regen_api": regen_api,
|
||||
"roles": roles,
|
||||
"lang": lang,
|
||||
}
|
||||
db_helper.update_user(user_id, user_data=user_data)
|
||||
user_crafty_data = {
|
||||
"permissions_mask": permissions_mask,
|
||||
"server_quantity": server_quantity
|
||||
}
|
||||
self.controller.users.update_user(user_id, user_data=user_data, user_crafty_data=user_crafty_data)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited user {} (UID:{}) with roles {}".format(username, user_id, roles),
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited user {} (UID:{}) with roles {} and permissions {}".format(username, user_id, roles, permissions_mask),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
self.redirect("/panel/panel_config")
|
||||
@ -556,17 +812,18 @@ class PanelHandler(BaseHandler):
|
||||
username = bleach.clean(self.get_argument('username', None))
|
||||
password0 = bleach.clean(self.get_argument('password0', None))
|
||||
password1 = bleach.clean(self.get_argument('password1', None))
|
||||
enabled = int(float(self.get_argument('enabled', '0')))
|
||||
enabled = int(float(self.get_argument('enabled', '0'))),
|
||||
lang = bleach.clean(self.get_argument('lang', 'en_EN'))
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a user editor")
|
||||
return
|
||||
elif username is None or username == "":
|
||||
self.redirect("/panel/error?error=Invalid username")
|
||||
return
|
||||
else:
|
||||
# does this user id exist?
|
||||
if db_helper.get_user_id_by_name(username) is not None:
|
||||
if self.controller.users.get_id_by_name(username) is not None:
|
||||
self.redirect("/panel/error?error=User exists")
|
||||
return
|
||||
|
||||
@ -575,7 +832,7 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
|
||||
roles = set()
|
||||
for role in db_helper.get_all_roles():
|
||||
for role in self.controller.roles.get_all_roles():
|
||||
argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('role_{}_membership'.format(role.role_id), '0')
|
||||
@ -584,24 +841,43 @@ class PanelHandler(BaseHandler):
|
||||
if argument:
|
||||
roles.add(role.role_id)
|
||||
|
||||
servers = set()
|
||||
for server in self.controller.list_defined_servers():
|
||||
permissions_mask = "000"
|
||||
server_quantity = {}
|
||||
for permission in self.controller.crafty_perms.list_defined_crafty_permissions():
|
||||
argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('server_{}_access'.format(server['server_id']), '0')
|
||||
self.get_argument('permission_{}'.format(permission.name), '0')
|
||||
)
|
||||
))
|
||||
if argument:
|
||||
servers.add(server['server_id'])
|
||||
permissions_mask = self.controller.crafty_perms.set_permission(permissions_mask, permission, argument)
|
||||
|
||||
user_id = db_helper.add_user(username, password=password0, enabled=enabled)
|
||||
db_helper.update_user(user_id, {"roles":roles})
|
||||
q_argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('quantity_{}'.format(permission.name), '0')
|
||||
)
|
||||
))
|
||||
if q_argument:
|
||||
server_quantity[permission.name] = q_argument
|
||||
else:
|
||||
server_quantity[permission.name] = 0
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
user_id = self.controller.users.add_user(username, password=password0, enabled=enabled)
|
||||
user_data = {
|
||||
"roles": roles,
|
||||
'lang': lang
|
||||
}
|
||||
user_crafty_data = {
|
||||
"permissions_mask": permissions_mask,
|
||||
"server_quantity": server_quantity
|
||||
}
|
||||
self.controller.users.update_user(user_id, user_data=user_data, user_crafty_data=user_crafty_data)
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Added user {} (UID:{})".format(username, user_id),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited user {} (UID:{}) with roles {}".format(username, user_id, roles),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
@ -611,8 +887,8 @@ class PanelHandler(BaseHandler):
|
||||
role_id = bleach.clean(self.get_argument('id', None))
|
||||
role_name = bleach.clean(self.get_argument('role_name', None))
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if Enum_Permissions_Crafty.Roles_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a role editor")
|
||||
return
|
||||
elif role_name is None or role_name == "":
|
||||
self.redirect("/panel/error?error=Invalid username")
|
||||
@ -622,7 +898,7 @@ class PanelHandler(BaseHandler):
|
||||
return
|
||||
else:
|
||||
# does this user id exist?
|
||||
if not db_helper.role_id_exists(role_id):
|
||||
if not self.controller.roles.role_id_exists(role_id):
|
||||
self.redirect("/panel/error?error=Invalid Role ID")
|
||||
return
|
||||
|
||||
@ -636,13 +912,23 @@ class PanelHandler(BaseHandler):
|
||||
if argument:
|
||||
servers.add(server['server_id'])
|
||||
|
||||
permissions_mask = "00000000"
|
||||
for permission in self.controller.server_perms.list_defined_permissions():
|
||||
argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('permission_{}'.format(permission.name), '0')
|
||||
)
|
||||
))
|
||||
if argument:
|
||||
permissions_mask = self.controller.server_perms.set_permission(permissions_mask, permission, argument)
|
||||
|
||||
role_data = {
|
||||
"role_name": role_name,
|
||||
"servers": servers
|
||||
}
|
||||
db_helper.update_role(role_id, role_data=role_data)
|
||||
self.controller.roles.update_role(role_id, role_data=role_data, permissions_mask=permissions_mask)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited role {} (RID:{}) with servers {}".format(role_name, role_id, servers),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
@ -652,15 +938,15 @@ class PanelHandler(BaseHandler):
|
||||
elif page == "add_role":
|
||||
role_name = bleach.clean(self.get_argument('role_name', None))
|
||||
|
||||
if not exec_user['superuser']:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not superuser")
|
||||
if Enum_Permissions_Crafty.Roles_Config not in exec_user_crafty_permissions:
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a role editor")
|
||||
return
|
||||
elif role_name is None or role_name == "":
|
||||
self.redirect("/panel/error?error=Invalid role name")
|
||||
return
|
||||
else:
|
||||
# does this user id exist?
|
||||
if db_helper.get_roleid_by_name(role_name) is not None:
|
||||
if self.controller.roles.get_roleid_by_name(role_name) is not None:
|
||||
self.redirect("/panel/error?error=Role exists")
|
||||
return
|
||||
|
||||
@ -674,14 +960,24 @@ class PanelHandler(BaseHandler):
|
||||
if argument:
|
||||
servers.add(server['server_id'])
|
||||
|
||||
role_id = db_helper.add_role(role_name)
|
||||
db_helper.update_role(role_id, {"servers": servers})
|
||||
permissions_mask = "00000000"
|
||||
for permission in self.controller.server_perms.list_defined_permissions():
|
||||
argument = int(float(
|
||||
bleach.clean(
|
||||
self.get_argument('permission_{}'.format(permission.name), '0')
|
||||
)
|
||||
))
|
||||
if argument:
|
||||
permissions_mask = self.controller.server_perms.set_permission(permissions_mask, permission, argument)
|
||||
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
role_id = self.controller.roles.add_role(role_name)
|
||||
self.controller.roles.update_role(role_id, {"servers": servers}, permissions_mask)
|
||||
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Added role {} (RID:{})".format(role_name, role_id),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
db_helper.add_to_audit_log(exec_user['user_id'],
|
||||
self.controller.management.add_to_audit_log(exec_user['user_id'],
|
||||
"Edited role {} (RID:{}) with servers {}".format(role_name, role_id, servers),
|
||||
server_id=0,
|
||||
source_ip=self.get_remote_ip())
|
||||
|
@ -7,7 +7,9 @@ import tornado.escape
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Users, fn, db_helper
|
||||
from app.classes.shared.main_models import fn
|
||||
|
||||
from app.classes.models.users import Users
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -15,8 +17,8 @@ try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -37,15 +39,14 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
def get(self, page=None):
|
||||
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
|
||||
error = bleach.clean(self.get_argument('error', "Invalid Login!"))
|
||||
|
||||
page_data = {
|
||||
'version': helper.get_version_string(),
|
||||
'error': error
|
||||
}
|
||||
|
||||
page_data['lang'] = tornado.locale.get("en_EN")
|
||||
|
||||
# sensible defaults
|
||||
template = "public/404.html"
|
||||
@ -59,9 +60,16 @@ class PublicHandler(BaseHandler):
|
||||
elif page == "error":
|
||||
template = "public/error.html"
|
||||
|
||||
elif page == "logout":
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
self.redirect('/public/login')
|
||||
return
|
||||
|
||||
# if we have no page, let's go to login
|
||||
else:
|
||||
self.redirect('/public/login')
|
||||
return
|
||||
|
||||
self.render(
|
||||
template,
|
||||
@ -81,15 +89,19 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
# if we don't have a user
|
||||
if not user_data:
|
||||
next_page = "/public/error?error=Login_Failed"
|
||||
next_page = "/public/error?error=Login Failed"
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
self.redirect(next_page)
|
||||
return False
|
||||
return
|
||||
|
||||
# if they are disabled
|
||||
if not user_data.enabled:
|
||||
next_page = "/public/error?error=Login_Failed"
|
||||
next_page = "/public/error?error=Login Failed"
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
self.redirect(next_page)
|
||||
return False
|
||||
return
|
||||
|
||||
login_result = helper.verify_pass(entered_password, user_data.password)
|
||||
|
||||
@ -105,7 +117,7 @@ class PublicHandler(BaseHandler):
|
||||
q.save()
|
||||
|
||||
# log this login
|
||||
db_helper.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())
|
||||
|
||||
cookie_data = {
|
||||
"username": user_data.username,
|
||||
@ -117,6 +129,12 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
next_page = "/panel/dashboard"
|
||||
self.redirect(next_page)
|
||||
else:
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
# log this failed login attempt
|
||||
self.controller.management.add_to_audit_log(user_data.user_id, "Tried to log in", 0, self.get_remote_ip())
|
||||
self.redirect('/public/error?error=Login Failed')
|
||||
else:
|
||||
self.redirect("/public/login")
|
||||
|
||||
|
@ -6,7 +6,7 @@ import shutil
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.models import db_helper, Servers
|
||||
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty
|
||||
from app.classes.minecraft.serverjars import server_jar_obj
|
||||
from app.classes.shared.helpers import helper
|
||||
|
||||
@ -19,8 +19,8 @@ try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -31,39 +31,58 @@ class ServerHandler(BaseHandler):
|
||||
# name = tornado.escape.json_decode(self.current_user)
|
||||
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
exec_user_id = exec_user_data['user_id']
|
||||
exec_user = db_helper.get_user(exec_user_id)
|
||||
|
||||
exec_user = self.controller.users.get_user_by_id(exec_user_id)
|
||||
|
||||
exec_user_role = set()
|
||||
if exec_user['superuser'] == 1:
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
exec_user_role.add("Super User")
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
|
||||
list_roles = []
|
||||
for role in self.controller.roles.get_all_roles():
|
||||
list_roles.append(self.controller.roles.get_role(role.role_id))
|
||||
else:
|
||||
defined_servers = self.controller.list_authorized_servers(exec_user_id)
|
||||
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user_id)
|
||||
defined_servers = self.controller.servers.get_authorized_servers(exec_user_id)
|
||||
list_roles = []
|
||||
for r in exec_user['roles']:
|
||||
role = db_helper.get_role(r)
|
||||
role = self.controller.roles.get_role(r)
|
||||
exec_user_role.add(role['role_name'])
|
||||
list_roles.append(self.controller.roles.get_role(role['role_id']))
|
||||
|
||||
template = "public/404.html"
|
||||
|
||||
defined_servers = self.controller.list_defined_servers()
|
||||
|
||||
page_data = {
|
||||
'version_data': helper.get_version_string(),
|
||||
'user_data': exec_user_data,
|
||||
'user_role' : exec_user_role,
|
||||
'roles' : list_roles,
|
||||
'user_crafty_permissions' : exec_user_crafty_permissions,
|
||||
'crafty_permissions': {
|
||||
'Server_Creation': Enum_Permissions_Crafty.Server_Creation,
|
||||
'User_Config': Enum_Permissions_Crafty.User_Config,
|
||||
'Roles_Config': Enum_Permissions_Crafty.Roles_Config,
|
||||
},
|
||||
'server_stats': {
|
||||
'total': len(self.controller.list_defined_servers()),
|
||||
'running': len(self.controller.list_running_servers()),
|
||||
'stopped': (len(self.controller.list_defined_servers()) - len(self.controller.list_running_servers()))
|
||||
},
|
||||
'hosts_data': db_helper.get_latest_hosts_stats(),
|
||||
'hosts_data': self.controller.management.get_latest_hosts_stats(),
|
||||
'menu_servers': defined_servers,
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True)
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True),
|
||||
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
|
||||
}
|
||||
if exec_user['superuser'] == 1:
|
||||
page_data['roles'] = list_roles
|
||||
|
||||
if page == "step1":
|
||||
if not exec_user['superuser'] and not self.controller.crafty_perms.can_create_server(exec_user_id):
|
||||
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
|
||||
return
|
||||
|
||||
page_data['server_types'] = server_jar_obj.get_serverjar_data()
|
||||
page_data['server_types'] = server_jar_obj.get_serverjar_data_sorted()
|
||||
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data_sorted())
|
||||
template = "server/wizard.html"
|
||||
|
||||
self.render(
|
||||
@ -77,13 +96,14 @@ class ServerHandler(BaseHandler):
|
||||
|
||||
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
|
||||
exec_user_id = exec_user_data['user_id']
|
||||
exec_user = db_helper.get_user(exec_user_id)
|
||||
exec_user = self.controller.users.get_user_by_id(exec_user_id)
|
||||
|
||||
template = "public/404.html"
|
||||
page_data = {
|
||||
'version_data': "version_data_here",
|
||||
'user_data': exec_user_data,
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True)
|
||||
'show_contribute': helper.get_setting("show_contribute_link", True),
|
||||
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
|
||||
}
|
||||
|
||||
if page == "command":
|
||||
@ -93,12 +113,12 @@ class ServerHandler(BaseHandler):
|
||||
if server_id is not None:
|
||||
if command == "clone_server":
|
||||
def is_name_used(name):
|
||||
for server in db_helper.get_all_defined_servers():
|
||||
for server in self.controller.servers.get_all_defined_servers():
|
||||
if server['server_name'] == name:
|
||||
return True
|
||||
return False
|
||||
|
||||
server_data = db_helper.get_server_data_by_id(server_id)
|
||||
return
|
||||
|
||||
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)"
|
||||
|
||||
@ -107,8 +127,6 @@ class ServerHandler(BaseHandler):
|
||||
name_counter += 1
|
||||
new_server_name = server_data.get('server_name') + " (Copy {})".format(name_counter)
|
||||
|
||||
console.debug('new_server_name: "{}"'.format(new_server_name))
|
||||
|
||||
new_server_uuid = helper.create_uuid()
|
||||
while os.path.exists(os.path.join(helper.servers_dir, new_server_uuid)):
|
||||
new_server_uuid = helper.create_uuid()
|
||||
@ -127,32 +145,20 @@ class ServerHandler(BaseHandler):
|
||||
crash_detection = server_data.get('crash_detection')
|
||||
server_port = server_data.get('server_port')
|
||||
|
||||
|
||||
# TODO create the server on the DB side
|
||||
|
||||
Servers.insert({
|
||||
Servers.server_name: new_server_name,
|
||||
Servers.server_uuid: new_server_uuid,
|
||||
Servers.path: new_server_path,
|
||||
Servers.executable: new_executable,
|
||||
Servers.execution_command: new_server_command,
|
||||
Servers.auto_start: auto_start,
|
||||
Servers.auto_start_delay: auto_start_delay,
|
||||
Servers.crash_detection: crash_detection,
|
||||
Servers.log_path: new_server_log_file,
|
||||
Servers.server_port: server_port,
|
||||
Servers.stop_command: stop_command
|
||||
}).execute()
|
||||
self.controller.servers.create_server(new_server_name, new_server_uuid, new_server_path, "", new_server_command, new_executable, new_server_log_file, stop_command, server_port)
|
||||
|
||||
self.controller.init_all_servers()
|
||||
console.debug('initted all servers')
|
||||
|
||||
return
|
||||
|
||||
db_helper.send_command(exec_user_data['user_id'], server_id, self.get_remote_ip(), command)
|
||||
|
||||
self.controller.management.send_command(exec_user_data['user_id'], server_id, self.get_remote_ip(), command)
|
||||
|
||||
if page == "step1":
|
||||
|
||||
if not exec_user['superuser']:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
else:
|
||||
user_roles = self.controller.roles.get_all_roles()
|
||||
server = bleach.clean(self.get_argument('server', ''))
|
||||
server_name = bleach.clean(self.get_argument('server_name', ''))
|
||||
min_mem = bleach.clean(self.get_argument('min_memory', ''))
|
||||
@ -162,44 +168,68 @@ class ServerHandler(BaseHandler):
|
||||
import_server_path = bleach.clean(self.get_argument('server_path', ''))
|
||||
import_server_jar = bleach.clean(self.get_argument('server_jar', ''))
|
||||
server_parts = server.split("|")
|
||||
captured_roles = []
|
||||
for role in user_roles:
|
||||
if bleach.clean(self.get_argument(str(role), '')) == "on":
|
||||
captured_roles.append(role)
|
||||
|
||||
if not server_name:
|
||||
self.redirect("/panel/error?error=Server name cannot be empty!")
|
||||
return
|
||||
|
||||
if import_type == 'import_jar':
|
||||
good_path = self.controller.verify_jar_server(import_server_path, import_server_jar)
|
||||
|
||||
if not good_path:
|
||||
self.redirect("/panel/error?error=Server path or Server Jar not found!")
|
||||
return False
|
||||
return
|
||||
|
||||
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
|
||||
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
|
||||
"imported a jar server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
elif import_type == 'import_zip':
|
||||
# here import_server_path means the zip path
|
||||
good_path = self.controller.verify_zip_server(import_server_path)
|
||||
if not good_path:
|
||||
self.redirect("/panel/error?error=Zip file not found!")
|
||||
return False
|
||||
return
|
||||
|
||||
new_server_id = self.controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
|
||||
if new_server_id == "false":
|
||||
self.redirect("/panel/error?error=ZIP file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
|
||||
return False
|
||||
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
|
||||
return
|
||||
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
|
||||
"imported a zip server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
else:
|
||||
# todo: add server type check here and call the correct server add functions if not a jar
|
||||
new_server_id = self.controller.create_jar_server(server_parts[0], server_parts[1], server_name, min_mem, max_mem, port)
|
||||
|
||||
if new_server_id is not None and exec_user_data is not None and len(server_parts) > 1:
|
||||
db_helper.add_to_audit_log(exec_user_data['user_id'],
|
||||
"created a {} {} server named \"{}\"".format(server_parts[1], str(server_parts[0]).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
|
||||
if len(server_parts) != 2:
|
||||
self.redirect("/panel/error?error=Invalid server data")
|
||||
return
|
||||
server_type, server_version = server_parts
|
||||
# TODO: add server type check here and call the correct server add functions if not a jar
|
||||
role_ids = self.controller.users.get_user_roles_id(exec_user_id)
|
||||
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
|
||||
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
|
||||
"created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
|
||||
elif new_server_id is not None and exec_user_data is not None:
|
||||
db_helper.add_to_audit_log(exec_user_data['user_id'],
|
||||
"created a {} {} server named \"{}\"".format("Minecraft", str(server_parts[0]).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
|
||||
new_server_id,
|
||||
self.get_remote_ip())
|
||||
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
|
||||
if len(captured_roles) == 0:
|
||||
if not exec_user['superuser']:
|
||||
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
|
||||
role_id = self.controller.roles.add_role("Creator of Server with uuid={}".format(new_server_uuid))
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
self.controller.users.add_role_to_user(exec_user_id, role_id)
|
||||
self.controller.crafty_perms.add_server_creation(exec_user_id)
|
||||
|
||||
else:
|
||||
logger.error("Unable to create server")
|
||||
console.error("Unable to create server")
|
||||
for role in captured_roles:
|
||||
role_id = role
|
||||
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
|
||||
|
||||
self.controller.stats.record_stats()
|
||||
self.redirect("/panel/dashboard")
|
||||
@ -208,4 +238,4 @@ class ServerHandler(BaseHandler):
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
)
|
||||
)
|
||||
|
15
app/classes/web/static_handler.py
Normal file
@ -0,0 +1,15 @@
|
||||
import tornado.web
|
||||
from typing import (
|
||||
Optional
|
||||
)
|
||||
|
||||
from app.classes.shared.console import console
|
||||
|
||||
class CustomStaticHandler(tornado.web.StaticFileHandler):
|
||||
def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
|
||||
try:
|
||||
return super().validate_absolute_path(root, absolute_path)
|
||||
except tornado.web.HTTPError as error:
|
||||
if 'HTTP 404: Not Found' in str(error):
|
||||
self.set_status(404)
|
||||
self.finish({'error':'NOT_FOUND', 'info':'The requested resource was not found on the server'})
|
56
app/classes/web/status_handler.py
Normal file
@ -0,0 +1,56 @@
|
||||
from re import template
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import tornado.web
|
||||
import tornado.escape
|
||||
import requests
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.main_models import fn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import bleach
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
|
||||
console.critical("Import Error: Unable to load {} module".format(e.name))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class StatusHandler(BaseHandler):
|
||||
def get(self):
|
||||
page_data = {}
|
||||
page_data['lang'] = tornado.locale.get("en_EN")
|
||||
page_data['servers'] = self.controller.servers.get_all_servers_stats()
|
||||
for srv in page_data['servers']:
|
||||
server_data = srv.get('server_data', False)
|
||||
server_id = server_data.get('server_id', False)
|
||||
srv['raw_ping_result'] = self.controller.stats.get_raw_server_stats(server_id)
|
||||
|
||||
template = 'public/status.html'
|
||||
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
)
|
||||
def post(self):
|
||||
page_data = {}
|
||||
page_data['servers'] = self.controller.servers.get_all_servers_stats()
|
||||
for srv in page_data['servers']:
|
||||
server_data = srv.get('server_data', False)
|
||||
server_id = server_data.get('server_id', False)
|
||||
srv['raw_ping_result'] = self.controller.stats.get_raw_server_stats(server_id)
|
||||
|
||||
template = 'public/status.html'
|
||||
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
translate=self.translator.translate,
|
||||
)
|
@ -25,7 +25,11 @@ try:
|
||||
from app.classes.web.ajax_handler import AjaxHandler
|
||||
from app.classes.web.api_handler import ServersStats, NodeStats
|
||||
from app.classes.web.websocket_handler import SocketHandler
|
||||
from app.classes.web.static_handler import CustomStaticHandler
|
||||
from app.classes.shared.translation import translation
|
||||
from app.classes.web.upload_handler import UploadHandler
|
||||
from app.classes.web.http_handler import HTTPHandler, HTTPHandlerPage
|
||||
from app.classes.web.status_handler import StatusHandler
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
@ -88,17 +92,13 @@ class Webserver:
|
||||
|
||||
http_port = helper.get_setting('http_port')
|
||||
https_port = helper.get_setting('https_port')
|
||||
|
||||
lang = helper.get_setting('language')
|
||||
|
||||
debug_errors = helper.get_setting('show_errors')
|
||||
cookie_secret = helper.get_setting('cookie_secret')
|
||||
|
||||
if cookie_secret is False:
|
||||
cookie_secret = helper.random_string_generator(32)
|
||||
|
||||
if not lang:
|
||||
lang = "en_EN"
|
||||
|
||||
if not http_port:
|
||||
http_port = 8000
|
||||
|
||||
@ -112,14 +112,11 @@ class Webserver:
|
||||
|
||||
logger.info("Starting Web Server on ports http:{} https:{}".format(http_port, https_port))
|
||||
|
||||
console.info("http://{}:{} is up and ready for connection:".format(helper.get_local_ip(), http_port))
|
||||
console.info("https://{}:{} is up and ready for connection:".format(helper.get_local_ip(), https_port))
|
||||
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
|
||||
tornado.template.Loader('.')
|
||||
|
||||
tornado.locale.set_default_locale(lang)
|
||||
tornado.locale.set_default_locale('en_EN')
|
||||
|
||||
handler_args = {"controller": self.controller, "tasks_manager": self.tasks_manager, "translator": translation}
|
||||
handlers = [
|
||||
@ -131,6 +128,8 @@ class Webserver:
|
||||
(r'/api/stats/servers', ServersStats, handler_args),
|
||||
(r'/api/stats/node', NodeStats, handler_args),
|
||||
(r'/ws', SocketHandler, handler_args),
|
||||
(r'/upload', UploadHandler, handler_args),
|
||||
(r'/status', StatusHandler, handler_args)
|
||||
]
|
||||
|
||||
app = tornado.web.Application(
|
||||
@ -143,18 +142,47 @@ class Webserver:
|
||||
autoreload=False,
|
||||
log_function=self.log_function,
|
||||
login_url="/login",
|
||||
default_handler_class=PublicHandler
|
||||
default_handler_class=PublicHandler,
|
||||
static_handler_class=CustomStaticHandler,
|
||||
serve_traceback=debug_errors,
|
||||
)
|
||||
HTTPhanders = [(r'/', HTTPHandler, handler_args),
|
||||
(r'/public/(.*)', HTTPHandlerPage, handler_args),
|
||||
(r'/panel/(.*)', HTTPHandlerPage, handler_args),
|
||||
(r'/server/(.*)', HTTPHandlerPage, handler_args),
|
||||
(r'/ajax/(.*)', HTTPHandlerPage, handler_args),
|
||||
(r'/api/stats/servers', HTTPHandlerPage, handler_args),
|
||||
(r'/api/stats/node', HTTPHandlerPage, handler_args),
|
||||
(r'/ws', HTTPHandlerPage, handler_args),
|
||||
(r'/upload', HTTPHandlerPage, handler_args)]
|
||||
HTTPapp = tornado.web.Application(
|
||||
HTTPhanders,
|
||||
template_path=os.path.join(helper.webroot, 'templates'),
|
||||
static_path=os.path.join(helper.webroot, 'static'),
|
||||
debug=debug_errors,
|
||||
cookie_secret=cookie_secret,
|
||||
xsrf_cookies=True,
|
||||
autoreload=False,
|
||||
log_function=self.log_function,
|
||||
default_handler_class = HTTPHandler,
|
||||
login_url="/login",
|
||||
serve_traceback=debug_errors,
|
||||
)
|
||||
|
||||
self.HTTP_Server = tornado.httpserver.HTTPServer(app)
|
||||
self.HTTP_Server = tornado.httpserver.HTTPServer(HTTPapp)
|
||||
self.HTTP_Server.listen(http_port)
|
||||
|
||||
self.HTTPS_Server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects)
|
||||
self.HTTPS_Server.listen(https_port)
|
||||
|
||||
logger.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
|
||||
logger.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
|
||||
console.info("http://{}:{} is up and ready for connections.".format(helper.get_local_ip(), http_port))
|
||||
console.info("https://{}:{} is up and ready for connections.".format(helper.get_local_ip(), https_port))
|
||||
|
||||
console.info("Server Init Complete: Listening For Connections:")
|
||||
|
||||
self.ioloop = tornado.ioloop.IOLoop.instance()
|
||||
self.ioloop = tornado.ioloop.IOLoop.current()
|
||||
self.ioloop.start()
|
||||
|
||||
def stop_web_server(self):
|
||||
|
88
app/classes/web/upload_handler.py
Normal file
@ -0,0 +1,88 @@
|
||||
from app.classes.shared.main_controller import Controller
|
||||
import tornado.options
|
||||
import tornado.web
|
||||
import tornado.httpserver
|
||||
from tornado.options import options
|
||||
from app.classes.models.server_permissions import Enum_Permissions_Server
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
from app.classes.shared.console import console
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Class&Function Defination
|
||||
MAX_STREAMED_SIZE = 1024 * 1024 * 1024
|
||||
|
||||
@tornado.web.stream_request_body
|
||||
class UploadHandler(tornado.web.RequestHandler):
|
||||
|
||||
def initialize(self, controller: Controller=None, tasks_manager=None, translator=None):
|
||||
self.controller = controller
|
||||
self.tasks_manager = tasks_manager
|
||||
self.translator = translator
|
||||
|
||||
def prepare(self):
|
||||
self.do_upload = True
|
||||
user_data = json.loads(self.get_secure_cookie('user_data'))
|
||||
user_id = user_data['user_id']
|
||||
|
||||
server_id = self.request.headers.get('X-ServerId', None)
|
||||
|
||||
if user_id is None:
|
||||
logger.warning('User ID not found in upload handler call')
|
||||
console.warning('User ID not found in upload handler call')
|
||||
self.do_upload = False
|
||||
|
||||
if server_id is None:
|
||||
logger.warning('Server ID not found in upload handler call')
|
||||
console.warning('Server ID not found in upload handler call')
|
||||
self.do_upload = False
|
||||
|
||||
user_permissions = self.controller.server_perms.get_user_permissions_list(user_id, server_id)
|
||||
if Enum_Permissions_Server.Files not in user_permissions:
|
||||
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
|
||||
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
|
||||
self.do_upload = False
|
||||
|
||||
path = self.request.headers.get('X-Path', None)
|
||||
filename = self.request.headers.get('X-FileName', None)
|
||||
full_path = os.path.join(path, filename)
|
||||
|
||||
if not helper.in_path(self.controller.servers.get_server_data_by_id(server_id)['path'], full_path):
|
||||
print(user_id, server_id, self.controller.servers.get_server_data_by_id(server_id)['path'], full_path)
|
||||
logger.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
|
||||
console.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
|
||||
self.do_upload = False
|
||||
|
||||
if self.do_upload:
|
||||
try:
|
||||
self.f = open(full_path, "wb")
|
||||
except Exception as e:
|
||||
logger.error("Upload failed with error: {}".format(e))
|
||||
self.do_upload = False
|
||||
# If max_body_size is not set, you cannot upload files > 100MB
|
||||
self.request.connection.set_max_body_size(MAX_STREAMED_SIZE)
|
||||
|
||||
def post(self):
|
||||
logger.info("Upload completed")
|
||||
files_left = int(self.request.headers.get('X-Files-Left', None))
|
||||
|
||||
if self.do_upload:
|
||||
time.sleep(5)
|
||||
if files_left == 0:
|
||||
websocket_helper.broadcast('close_upload_box', 'success')
|
||||
self.finish('success') # Nope, I'm sending "success"
|
||||
self.f.close()
|
||||
else:
|
||||
time.sleep(5)
|
||||
if files_left == 0:
|
||||
websocket_helper.broadcast('close_upload_box', 'error')
|
||||
self.finish('error')
|
||||
|
||||
def data_received(self, data):
|
||||
if self.do_upload:
|
||||
self.f.write(data)
|
@ -1,13 +1,21 @@
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
import tornado.websocket
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Users, db_helper
|
||||
from urllib.parse import parse_qsl
|
||||
from app.classes.models.users import Users
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.web.websocket_helper import websocket_helper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import tornado.websocket
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
|
||||
@ -15,6 +23,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
self.controller = controller
|
||||
self.tasks_manager = tasks_manager
|
||||
self.translator = translator
|
||||
self.io_loop = tornado.ioloop.IOLoop.current()
|
||||
|
||||
def get_remote_ip(self):
|
||||
remote_ip = self.request.headers.get("X-Real-IP") or \
|
||||
@ -35,17 +44,23 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
|
||||
|
||||
def open(self):
|
||||
logger.debug('Checking WebSocket authentication')
|
||||
if self.check_auth():
|
||||
self.handle()
|
||||
else:
|
||||
websocket_helper.send_message(self, 'notification', 'Not authenticated for WebSocket connection')
|
||||
self.close()
|
||||
db_helper.add_to_audit_log_raw('unknown', 0, 0, 'Someone tried to connect via WebSocket without proper authentication', self.get_remote_ip())
|
||||
self.controller.management.add_to_audit_log_raw('unknown', 0, 0, 'Someone tried to connect via WebSocket without proper authentication', self.get_remote_ip())
|
||||
websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication')
|
||||
logger.warning('Someone tried to connect via WebSocket without proper authentication')
|
||||
|
||||
def handle(self):
|
||||
|
||||
websocket_helper.addClient(self)
|
||||
self.page = self.get_query_argument('page')
|
||||
self.page_query_params = dict(parse_qsl(helper.remove_prefix(
|
||||
self.get_query_argument('page_query_params'),
|
||||
'?'
|
||||
)))
|
||||
websocket_helper.add_client(self)
|
||||
logger.debug('Opened WebSocket connection')
|
||||
# websocket_helper.broadcast('notification', 'New client connected')
|
||||
|
||||
@ -56,7 +71,13 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
logger.debug('Event Type: {}, Data: {}'.format(message['event'], message['data']))
|
||||
|
||||
def on_close(self):
|
||||
websocket_helper.removeClient(self)
|
||||
websocket_helper.remove_client(self)
|
||||
logger.debug('Closed WebSocket connection')
|
||||
# websocket_helper.broadcast('notification', 'Client disconnected')
|
||||
|
||||
async def write_message_int(self, message):
|
||||
self.write_message(message)
|
||||
|
||||
def write_message_helper(self, message):
|
||||
asyncio.run_coroutine_threadsafe(self.write_message_int(message), self.io_loop.asyncio_loop)
|
||||
|
||||
|
@ -1,31 +1,75 @@
|
||||
import json
|
||||
import logging
|
||||
import sys, threading, asyncio
|
||||
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class WebSocketHelper:
|
||||
clients = set()
|
||||
|
||||
def addClient(self, client):
|
||||
try:
|
||||
import tornado.ioloop
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
class WebSocketHelper:
|
||||
def __init__(self):
|
||||
self.clients = set()
|
||||
|
||||
def add_client(self, client):
|
||||
self.clients.add(client)
|
||||
|
||||
def removeClient(self, client):
|
||||
self.clients.add(client)
|
||||
|
||||
def send_message(self, client, event_type, data):
|
||||
def remove_client(self, client):
|
||||
self.clients.remove(client)
|
||||
|
||||
def send_message(self, client, event_type: str, data):
|
||||
if client.check_auth():
|
||||
message = str(json.dumps({'event': event_type, 'data': data}))
|
||||
client.write_message(message)
|
||||
client.write_message_helper(message)
|
||||
|
||||
def broadcast(self, event_type, data):
|
||||
logger.debug('Sending: ' + str(json.dumps({'event': event_type, 'data': data})))
|
||||
def broadcast(self, event_type: str, data):
|
||||
logger.debug('Sending to {} clients: {}'.format(len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
for client in self.clients:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
|
||||
|
||||
def broadcast_page(self, page: str, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
return client.page == page
|
||||
|
||||
clients = list(filter(filter_fn, self.clients))
|
||||
|
||||
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
|
||||
for client in clients:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except Exception as e:
|
||||
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
|
||||
|
||||
def broadcast_page_params(self, page: str, params: dict, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if client.page != page:
|
||||
return False
|
||||
for key, param in params.items():
|
||||
if param != client.page_query_params.get(key, None):
|
||||
return False
|
||||
return True
|
||||
|
||||
clients = list(filter(filter_fn, self.clients))
|
||||
|
||||
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
|
||||
|
||||
for client in clients:
|
||||
try:
|
||||
self.send_message(client, event_type, data)
|
||||
except Exception as e:
|
||||
logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
|
||||
|
||||
def disconnect_all(self):
|
||||
console.info('Disconnecting WebSocket clients')
|
||||
|
@ -10,7 +10,7 @@
|
||||
"stats_update_frequency": 30,
|
||||
"delete_default_json": false,
|
||||
"show_contribute_link": true,
|
||||
"virtual_terminal_lines": 10,
|
||||
"virtual_terminal_lines": 70,
|
||||
"max_log_lines": 700,
|
||||
"keywords": ["help", "chunk"]
|
||||
}
|
||||
}
|
||||
|
@ -1,150 +1,301 @@
|
||||
{
|
||||
"patreons": [
|
||||
{
|
||||
"name": "Dean of Approval",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Lightkeeper",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Lino",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas T",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Richard B",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "ryuk228",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Zedifus",
|
||||
"level": "sustainer"
|
||||
},
|
||||
{
|
||||
"name": "John C",
|
||||
"level": "advocate"
|
||||
}
|
||||
],
|
||||
"staff": {
|
||||
"development": [
|
||||
{
|
||||
"name": "Phil Tarrant",
|
||||
"title": "Benevolent Dictator for Life",
|
||||
"loc": "Atlanta, GA",
|
||||
"tags": [ "Staff", "Developer", [ "BDFL", "https://en.wikipedia.org/wiki/Benevolent_dictator_for_life" ] ],
|
||||
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
|
||||
"pic": "/static/assets/images/credits/ptarrant_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "Pita Bread",
|
||||
"title": null,
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [ "Staff", null, "Community Leader" ],
|
||||
"blurb": "His interests include bread, Linux, and networking. He enjoys pumpkins, organizing, and long-winded emails, but hates wifi.",
|
||||
"pic": "/static/assets/images/credits/pita_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "macgeek",
|
||||
"title": null,
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [ "Staff", "Developer", "Project Manager" ],
|
||||
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
|
||||
"pic": "/static/assets/images/credits/macgeek_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "parzivaldewey",
|
||||
"title": null,
|
||||
"loc": "East Coast, USA",
|
||||
"tags": [ "Staff", "Developer", "Support Manager" ],
|
||||
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
|
||||
"pic": "/static/assets/images/credits/andrew_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "MC Gaming",
|
||||
"title": null,
|
||||
"loc": "Central, UK",
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "His interests include learning, Linux, programming. He loves pentesting apps and gaming.",
|
||||
"pic": "/static/assets/images/credits/mcgaming.png"
|
||||
},
|
||||
{
|
||||
"name": "Silversthorn",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "Often in it's cave, he sometimes goes out to help or do silly jokes. He's an IT clown (not the film), but seriously do the job when it's needed.",
|
||||
"pic": "/static/assets/images/credits/silversthorn.png"
|
||||
},
|
||||
{
|
||||
"name": "ThatOneLukas",
|
||||
"title": null,
|
||||
"loc": "Helsinki, FI",
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "Lukas enjoys bashing his head at the table while his code does not work",
|
||||
"pic": "/static/assets/images/credits/lukas_cropped.png"
|
||||
}
|
||||
],
|
||||
"support": [
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [ "Staff", "Wiki", null ],
|
||||
"blurb": "Silver enjoys helping others with their computer needs, writing documentation and loving her cat.",
|
||||
"pic": "/static/assets/images/credits/isilverfyre.png"
|
||||
},
|
||||
{
|
||||
"name": "Quentin",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [ "Staff", "Developer", null ],
|
||||
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
|
||||
"pic": "/static/assets/images/credits/qub3d.png"
|
||||
}
|
||||
],
|
||||
"retired": [
|
||||
{
|
||||
"name": "Kev Dagoat",
|
||||
"title": "Head of Development",
|
||||
"loc": "East Coast, AU",
|
||||
"tags": [ "Staff", "Developer", "HOD" ],
|
||||
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and Geeking over video cards.",
|
||||
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
|
||||
},
|
||||
{
|
||||
"name": "Manu",
|
||||
"title": null,
|
||||
"loc": "Eastern, CA",
|
||||
"tags": [ "Staff", "Developer", "Project Manager" ],
|
||||
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
|
||||
"pic": "/static/assets/images/credits/manu_cropped.png"
|
||||
},
|
||||
{
|
||||
"name": "UltraBlack",
|
||||
"title": null,
|
||||
"loc": "Bavaria, DE",
|
||||
"tags": [ "Staff", null, "Idea Manager" ],
|
||||
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
|
||||
"pic": "/static/assets/images/credits/ultrablack_cropped.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"translations": {
|
||||
"Lukas": [ "Finnish" ],
|
||||
"Manu": [ "French" ],
|
||||
"ptarrant": [ "Sarcasm", "Wit" ],
|
||||
"UltraBlack": [ "German" ]
|
||||
}
|
||||
}
|
||||
"staff": {
|
||||
"development": [
|
||||
{
|
||||
"name": "Phil Tarrant",
|
||||
"title": "Creator",
|
||||
"loc": "Southeast, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Ptarrant1"
|
||||
],
|
||||
"Creator"
|
||||
],
|
||||
"blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.",
|
||||
"pic": "/static/assets/images/credits/ptarrant.png"
|
||||
},
|
||||
{
|
||||
"name": "Pita Bread",
|
||||
"title": "Leadership Team",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/craftbreadth"
|
||||
],
|
||||
"Community Leader"
|
||||
],
|
||||
"blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.",
|
||||
"pic": "/static/assets/images/credits/pita.png"
|
||||
},
|
||||
{
|
||||
"name": "macgeek",
|
||||
"title": "Leadership Team",
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/computergeek125"
|
||||
],
|
||||
"Project Manager"
|
||||
],
|
||||
"blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.",
|
||||
"pic": "/static/assets/images/credits/macgeek.png"
|
||||
},
|
||||
{
|
||||
"name": "parzivaldewey",
|
||||
"title": null,
|
||||
"loc": "East Coast, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/amcmanu3"
|
||||
],
|
||||
"Support Manager"
|
||||
],
|
||||
"blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.",
|
||||
"pic": "/static/assets/images/credits/parzivaldewey.png"
|
||||
},
|
||||
{
|
||||
"name": "Xithical",
|
||||
"title": null,
|
||||
"loc": "Midwest, USA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/xithical"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.",
|
||||
"pic": "/static/assets/images/credits/xithical.png"
|
||||
},
|
||||
{
|
||||
"name": "MC Gaming",
|
||||
"title": null,
|
||||
"loc": "Central, UK",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/MCgamin1738"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.",
|
||||
"pic": "/static/assets/images/credits/mcgaming.png"
|
||||
},
|
||||
{
|
||||
"name": "Silversthorn",
|
||||
"title": null,
|
||||
"loc": "Provence, FR",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Silversthorn"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Developper at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"",
|
||||
"pic": "/static/assets/images/credits/silversthorn.png"
|
||||
},
|
||||
{
|
||||
"name": "ThatOneLukas",
|
||||
"title": null,
|
||||
"loc": "Helsinki, FI",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/LukasDoesDev"
|
||||
],
|
||||
null
|
||||
],
|
||||
"blurb": "Lukas enjoys bashing his head at the table while his code does not work",
|
||||
"pic": "/static/assets/images/credits/lukas.png"
|
||||
},
|
||||
{
|
||||
"name": "Zedifus",
|
||||
"title": null,
|
||||
"loc": "Scotland, UK",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/Zedifus"
|
||||
],
|
||||
"DevOps"
|
||||
],
|
||||
"blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.",
|
||||
"pic": "/static/assets/images/credits/zedifus.jpg"
|
||||
}
|
||||
],
|
||||
"support": [
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Wiki",
|
||||
null
|
||||
],
|
||||
"blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.",
|
||||
"pic": "/static/assets/images/credits/isilverfyre.png"
|
||||
},
|
||||
{
|
||||
"name": "Quentin",
|
||||
"title": null,
|
||||
"loc": null,
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Developer",
|
||||
null
|
||||
],
|
||||
"blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.",
|
||||
"pic": "/static/assets/images/credits/qub3d.png"
|
||||
}
|
||||
],
|
||||
"retired": [
|
||||
{
|
||||
"name": "Kev Dagoat",
|
||||
"title": "Head of Development",
|
||||
"loc": "East Coast, AU",
|
||||
"tags": [
|
||||
"Staff",
|
||||
[
|
||||
"Developer",
|
||||
"https://gitlab.com/kevdagoat"
|
||||
],
|
||||
"HOD"
|
||||
],
|
||||
"blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.",
|
||||
"pic": "/static/assets/images/credits/kevdagoat.jpeg"
|
||||
},
|
||||
{
|
||||
"name": "Manu",
|
||||
"title": null,
|
||||
"loc": "Eastern, CA",
|
||||
"tags": [
|
||||
"Staff",
|
||||
"Developer",
|
||||
"Project Manager"
|
||||
],
|
||||
"blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.",
|
||||
"pic": "/static/assets/images/credits/manu.png"
|
||||
},
|
||||
{
|
||||
"name": "UltraBlack",
|
||||
"title": null,
|
||||
"loc": "Bavaria, DE",
|
||||
"tags": [
|
||||
"Staff",
|
||||
null,
|
||||
"Idea Manager"
|
||||
],
|
||||
"blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.",
|
||||
"pic": "/static/assets/images/credits/ultrablack.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"translations": {
|
||||
"ptarrant": [
|
||||
"Sarcasm",
|
||||
"Wit"
|
||||
],
|
||||
"Lukas": [
|
||||
"Finnish"
|
||||
],
|
||||
"Manu": [
|
||||
"French"
|
||||
],
|
||||
"Silversthorn": [
|
||||
"French"
|
||||
],
|
||||
"UltraBlack": [
|
||||
"German"
|
||||
]
|
||||
},
|
||||
"patrons": [
|
||||
{
|
||||
"name": "Richard B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "JOHN C",
|
||||
"level": "Crafty Advocate"
|
||||
},
|
||||
{
|
||||
"name": "iSilverfyre",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Dean R",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "scott m",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "zedifus",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Lino",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "PeterPorker3",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "Ewari",
|
||||
"level": "Crafty Supporter"
|
||||
},
|
||||
{
|
||||
"name": "Thyodas",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "AngryPanda",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Chris T",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Adam B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Eric G",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Chris B",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Emmet d",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Steven T",
|
||||
"level": "Crafty Sustainer"
|
||||
},
|
||||
{
|
||||
"name": "Row H",
|
||||
"level": "Crafty Supporter"
|
||||
}
|
||||
],
|
||||
"lastUpdate": 1637281075498
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
"tornado_access": {
|
||||
"format": "%(asctime)s - [Tornado] - [Access] - %(levelname)s - %(message)s"
|
||||
},
|
||||
"schedule": {
|
||||
"schedule": {
|
||||
"format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s"
|
||||
}
|
||||
},
|
||||
|
24
app/config/motd_format.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"dark_red":"§4",
|
||||
"red":"§c",
|
||||
"gold":"§6",
|
||||
"yellow":"§e",
|
||||
"dark_green":"§2",
|
||||
"green":"§a",
|
||||
"aqua":"§b",
|
||||
"dark_aqua":"§3",
|
||||
"dark_blue":"§1",
|
||||
"blue":"§9",
|
||||
"light_purple":"§d",
|
||||
"dark_purple":"§5",
|
||||
"white":"§f",
|
||||
"gray":"§7",
|
||||
"dark_gray":"§8",
|
||||
"black":"§0",
|
||||
"reset":"§r",
|
||||
"bold":"§l",
|
||||
"italic":"§o",
|
||||
"underlined":"§n",
|
||||
"strikethrough":"§m",
|
||||
"obfuscated":"§k"
|
||||
}
|
@ -2,5 +2,5 @@
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": 0,
|
||||
"meta": "alpha.2"
|
||||
"meta": "alpha.3"
|
||||
}
|
||||
|
@ -79,4 +79,9 @@ body { background-color: var(--dark) !important; /* Firefox */ }
|
||||
|
||||
.actions_serverlist > a > i {
|
||||
cursor: pointer;
|
||||
}
|
||||
.corner {
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
}
|
@ -16188,41 +16188,35 @@ body.avgrund-active {
|
||||
border-radius: 2px; }
|
||||
|
||||
/* Context Menu */
|
||||
.context-menu-icon:before {
|
||||
color: #000;
|
||||
font: normal normal normal 15px/1 "Material Design Icons"; }
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
background: lightgray;
|
||||
border: 1px solid black;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.context-menu-icon.context-menu-icon-cut:before {
|
||||
content: '\F190'; }
|
||||
.context-menu ul {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
min-width: 150px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.context-menu-icon.context-menu-icon-edit:before {
|
||||
content: '\F3EF'; }
|
||||
.context-menu ul li {
|
||||
padding-bottom: 7px;
|
||||
padding-top: 7px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.context-menu-icon.context-menu-icon-copy:before {
|
||||
content: '\F18F'; }
|
||||
|
||||
.context-menu-icon.context-menu-icon-paste:before {
|
||||
content: '\F613'; }
|
||||
|
||||
.context-menu-icon.context-menu-icon-delete:before {
|
||||
content: '\F6CB'; }
|
||||
|
||||
.context-menu-icon.context-menu-icon-quit:before {
|
||||
content: '\F156'; }
|
||||
|
||||
.context-menu-list {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
border: 1px solid #dee2e6; }
|
||||
.context-menu-list .context-menu-item span {
|
||||
color: #000;
|
||||
font-size: 0.75rem;
|
||||
font-family: "roboto", sans-serif; }
|
||||
.context-menu-list .context-menu-item.context-menu-hover {
|
||||
background: #000; }
|
||||
.context-menu-list .context-menu-item.context-menu-hover span {
|
||||
color: #fff; }
|
||||
.context-menu ul li a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.context-menu ul li:hover {
|
||||
background: darkgray;
|
||||
}
|
||||
/* Clockpicker */
|
||||
.clockpicker-popover {
|
||||
background-color: #dee2e6; }
|
||||
|
BIN
app/frontend/static/assets/images/Crafty_4-0_Logo_square.ico
Normal file
After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 413 KiB |
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 243 KiB |
Before Width: | Height: | Size: 558 KiB After Width: | Height: | Size: 558 KiB |
Before Width: | Height: | Size: 842 KiB After Width: | Height: | Size: 842 KiB |
BIN
app/frontend/static/assets/images/credits/ptarrant.png
Normal file
After Width: | Height: | Size: 311 KiB |
Before Width: | Height: | Size: 5.6 MiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 856 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
BIN
app/frontend/static/assets/images/credits/xithical.png
Normal file
After Width: | Height: | Size: 420 KiB |
BIN
app/frontend/static/assets/images/credits/zedifus.jpg
Normal file
After Width: | Height: | Size: 246 KiB |
BIN
app/frontend/static/assets/images/logo_long.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
app/frontend/static/assets/images/pack.png
Normal file
After Width: | Height: | Size: 25 KiB |
127
app/frontend/static/assets/js/motd.js
Normal file
@ -0,0 +1,127 @@
|
||||
var obfuscators = [];
|
||||
var styleMap = {
|
||||
'§0': 'color:#000000',
|
||||
'§1': 'color:#0000AA',
|
||||
'§2': 'color:#00AA00',
|
||||
'§3': 'color:#00AAAA',
|
||||
'§4': 'color:#AA0000',
|
||||
'§5': 'color:#AA00AA',
|
||||
'§6': 'color:#FFAA00',
|
||||
'§7': 'color:#AAAAAA',
|
||||
'§8': 'color:#555555',
|
||||
'§9': 'color:#5555FF',
|
||||
'§a': 'color:#55FF55',
|
||||
'§b': 'color:#55FFFF',
|
||||
'§c': 'color:#FF5555',
|
||||
'§d': 'color:#FF55FF',
|
||||
'§e': 'color:#FFFF55',
|
||||
'§f': 'color:#FFFFFF',
|
||||
'§l': 'font-weight:bold',
|
||||
'§m': 'text-decoration:line-through',
|
||||
'§n': 'text-decoration:underline',
|
||||
'§o': 'font-style:italic',
|
||||
};
|
||||
function obfuscate(string, elem) {
|
||||
var magicSpan,
|
||||
currNode;
|
||||
if(string.indexOf('<br>') > -1) {
|
||||
elem.innerHTML = string;
|
||||
for(var j = 0, len = elem.childNodes.length; j < len; j++) {
|
||||
currNode = elem.childNodes[j];
|
||||
if(currNode.nodeType === 3) {
|
||||
magicSpan = document.createElement('span');
|
||||
magicSpan.innerHTML = currNode.nodeValue;
|
||||
elem.replaceChild(magicSpan, currNode);
|
||||
init(magicSpan);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
init(elem, string);
|
||||
}
|
||||
function init(el, str) {
|
||||
var i = 0,
|
||||
obsStr = str || el.innerHTML,
|
||||
len = obsStr.length;
|
||||
obfuscators.push( window.setInterval(function () {
|
||||
if(i >= len) i = 0;
|
||||
obsStr = replaceRand(obsStr, i);
|
||||
el.innerHTML = obsStr;
|
||||
i++;
|
||||
}, 0) );
|
||||
}
|
||||
function randInt(min, max) {
|
||||
return Math.floor( Math.random() * (max - min + 1) ) + min;
|
||||
}
|
||||
function replaceRand(string, i) {
|
||||
var randChar = String.fromCharCode( randInt(64, 95) );
|
||||
return string.substr(0, i) + randChar + string.substr(i + 1, string.length);
|
||||
}
|
||||
}
|
||||
function applyCode(string, codes) {
|
||||
var elem = document.createElement('span'),
|
||||
obfuscated = false;
|
||||
string = string.replace(/\x00*/g, '');
|
||||
for(var i = 0, len = codes.length; i < len; i++) {
|
||||
elem.style.cssText += styleMap[codes[i]] + ';';
|
||||
if(codes[i] === '§k') {
|
||||
obfuscate(string, elem);
|
||||
obfuscated = true;
|
||||
}
|
||||
}
|
||||
if(!obfuscated) elem.innerHTML = string;
|
||||
return elem;
|
||||
}
|
||||
function parseStyle(string) {
|
||||
var codes = string.match(/§.{1}/g) || [],
|
||||
indexes = [],
|
||||
apply = [],
|
||||
tmpStr,
|
||||
deltaIndex,
|
||||
noCode,
|
||||
final = document.createDocumentFragment(),
|
||||
i;
|
||||
string = string.replace(/\n|\\n/g, '<br>');
|
||||
for(i = 0, len = codes.length; i < len; i++) {
|
||||
indexes.push( string.indexOf(codes[i]) );
|
||||
string = string.replace(codes[i], '\x00\x00');
|
||||
}
|
||||
if(indexes[0] !== 0) {
|
||||
final.appendChild( applyCode( string.substring(0, indexes[0]), [] ) );
|
||||
}
|
||||
for(i = 0; i < len; i++) {
|
||||
indexDelta = indexes[i + 1] - indexes[i];
|
||||
if(indexDelta === 2) {
|
||||
while(indexDelta === 2) {
|
||||
apply.push ( codes[i] );
|
||||
i++;
|
||||
indexDelta = indexes[i + 1] - indexes[i];
|
||||
}
|
||||
apply.push ( codes[i] );
|
||||
} else {
|
||||
apply.push( codes[i] );
|
||||
}
|
||||
if( apply.lastIndexOf('§r') > -1) {
|
||||
apply = apply.slice( apply.lastIndexOf('§r') + 1 );
|
||||
}
|
||||
tmpStr = string.substring( indexes[i], indexes[i + 1] );
|
||||
final.appendChild( applyCode(tmpStr, apply) );
|
||||
}
|
||||
return final;
|
||||
}
|
||||
function clearObfuscators() {
|
||||
var i = obfuscators.length;
|
||||
for(;i--;) {
|
||||
clearInterval(obfuscators[i]);
|
||||
}
|
||||
obfuscators = [];
|
||||
}
|
||||
function initParser(input, output) {
|
||||
clearObfuscators();
|
||||
var input = document.getElementById(input),
|
||||
output = document.getElementById(output);
|
||||
if (input != null && output != null) {
|
||||
var parsed = parseStyle( input.innerHTML );
|
||||
output.innerHTML = '';
|
||||
output.appendChild(parsed);
|
||||
}
|
||||
}
|
@ -60,7 +60,7 @@
|
||||
|
||||
<div class="warnings">
|
||||
<noscript class="noscript-warning" style="padding: 20px; background-color: rgb(247, 151, 15);">
|
||||
<div>{% raw translate('base', 'doesNotWorkWithoutJavascript') %}</div>
|
||||
<div>{% raw translate('base', 'doesNotWorkWithoutJavascript', data['lang']) %}</div>
|
||||
</noscript>
|
||||
</div>
|
||||
|
||||
@ -134,12 +134,12 @@
|
||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||
<script src="/static/assets/js/shared/misc.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
$.extend($.fn.dataTable.defaults, {
|
||||
language: {% raw translate('datatables', 'i18n') %}
|
||||
language: {% raw translate('datatables', 'i18n', data['lang']) %}
|
||||
})
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
@ -173,8 +173,9 @@
|
||||
let listenEvents = [];
|
||||
|
||||
try {
|
||||
|
||||
var wsInternal = new WebSocket('wss://' + location.host + '/ws');
|
||||
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
|
||||
page = 'page=' + encodeURIComponent(location.pathname)
|
||||
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
|
||||
wsInternal.onopen = function() {
|
||||
console.log('opened WebSocket connection:', wsInternal)
|
||||
};
|
||||
@ -219,6 +220,24 @@
|
||||
var webSocket;
|
||||
// {% end%}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_error', function (start_error) {
|
||||
var x = document.querySelector('.bootbox');
|
||||
if(x){
|
||||
x.remove()}
|
||||
var x = document.querySelector('.modal-backdrop');
|
||||
if(x){
|
||||
x.remove()}
|
||||
bootbox.alert({
|
||||
message: start_error.error,
|
||||
callback: function () {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function warn(message) {
|
||||
var closeEl = document.createElement('span');
|
||||
var strongEL = document.createElement('strong');
|
||||
|
@ -11,6 +11,8 @@
|
||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
|
||||
<link rel="stylesheet" href="/static/assest/css/crafty.css">
|
||||
<!-- endinject -->
|
||||
<!-- Plugin css for this page -->
|
||||
<!-- End Plugin css for this page -->
|
||||
@ -24,7 +26,7 @@
|
||||
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
|
||||
<div class="row w-100">
|
||||
<div class="col-lg-4 mx-auto">
|
||||
<div class="mx-auto">
|
||||
|
||||
{% block content %}
|
||||
{% end %}
|
||||
@ -47,5 +49,11 @@
|
||||
<script src="/static/assets/js/shared/settings.js"></script>
|
||||
<script src="/static/assets/js/shared/todolist.js"></script>
|
||||
<!-- endinject -->
|
||||
|
||||
{% block js %}
|
||||
<!-- Custom js for this page -->
|
||||
<!-- End custom js for this page -->
|
||||
{% end %}
|
||||
|
||||
</body>
|
||||
</html>
|
@ -2,8 +2,8 @@
|
||||
<footer class="footer">
|
||||
<div class="container-fluid ">
|
||||
|
||||
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright') }} © 2021 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved') }}.</span>
|
||||
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version') }}: {{ data['version_data'] }}
|
||||
<span class="text-muted d-block text-center text-sm-left d-sm-inline-block">{{ translate('footer', 'copyright', data['lang']) }} © 2021 <a href="http://www.craftycontrol.com/" target="_blank">Crafty Controller</a>. {{ translate('footer', 'allRightsReserved', data['lang']) }}.</span>
|
||||
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">{{ translate('footer', 'version', data['lang']) }}: {{ data['version_data'] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -4,26 +4,28 @@
|
||||
<nav class="sidebar sidebar-offcanvas" id="sidebar">
|
||||
<ul class="nav">
|
||||
|
||||
<li class="nav-item nav-category" style="margin-top:10px;">{{ translate('sidebar', 'navigation') }}</li>
|
||||
<li class="nav-item nav-category" style="margin-top:10px;">{{ translate('sidebar', 'navigation', data['lang']) }}</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/dashboard">
|
||||
<i class="fas fa-chart-network"></i>
|
||||
<span class="menu-title">{{ translate('sidebar', 'dashboard') }}</span>
|
||||
<span class="menu-title">{{ translate('sidebar', 'dashboard', data['lang']) }}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="collapse" href="#page-layouts" aria-expanded="false" aria-controls="page-layouts">
|
||||
<i class="fas fa-server"></i>
|
||||
<span class="menu-title">{{ translate('sidebar', 'servers') }}</span>
|
||||
<span class="menu-title">{{ translate('sidebar', 'servers', data['lang']) }}</span>
|
||||
<i class="menu-arrow"></i>
|
||||
</a>
|
||||
<div class="collapse" id="page-layouts">
|
||||
<ul class="nav flex-column sub-menu">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{ translate('sidebar', 'newServer') }}</a>
|
||||
{% if data['crafty_permissions']['Server_Creation'] in data['user_crafty_permissions'] %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{ translate('sidebar', 'newServer', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% for s in data['menu_servers'] %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{s['server_id']}}"><i class="fas fa-server"></i> {{s['server_name']}}</a>
|
||||
@ -37,7 +39,7 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://gitlab.com/crafty-controller/crafty-web/-/wikis/home" target="_blank">
|
||||
<i class="fas fa-book"></i>
|
||||
<span class="menu-title">{{ translate('sidebar', 'documentation') }}</span>
|
||||
<span class="menu-title">{{ translate('sidebar', 'documentation', data['lang']) }}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -51,7 +53,7 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/credits">
|
||||
<i class="fas fa-heart"></i>
|
||||
<span class="menu-title">{{ translate('sidebar', 'credits') }}</span>
|
||||
<span class="menu-title">{{ translate('sidebar', 'credits', data['lang']) }}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -59,7 +61,7 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/contribute">
|
||||
<i class="fas fa-donate"></i>
|
||||
<span class="menu-title">{{ translate('sidebar', 'contribute') }}</span>
|
||||
<span class="menu-title">{{ translate('sidebar', 'contribute', data['lang']) }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% end %}
|
||||
|
@ -28,8 +28,10 @@
|
||||
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
|
||||
{% end %}
|
||||
</div>
|
||||
{% if "Super User" in data['user_role'] %}
|
||||
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i> Activity</a>
|
||||
<a class="dropdown-item" href="/public/login"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>Sign Out</a>
|
||||
{% end %}
|
||||
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>Sign Out</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
@ -64,7 +64,11 @@
|
||||
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
|
||||
{% end %}
|
||||
{% if person['tags'][1] %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% if type(person['tags'][1]) is list %}
|
||||
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][1] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% if person['tags'][2] %}
|
||||
{% if type(person['tags'][2]) is list %}
|
||||
@ -75,11 +79,11 @@
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex align-items-start pt-3">
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
Crafty's {{ person['title'] }}<br /><br />
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
{{ person['blurb'] }}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -129,7 +133,11 @@
|
||||
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
|
||||
{% end %}
|
||||
{% if person['tags'][1] %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% if type(person['tags'][1]) is list %}
|
||||
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][1] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% if person['tags'][2] %}
|
||||
{% if type(person['tags'][2]) is list %}
|
||||
@ -140,11 +148,11 @@
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex align-items-start pt-3">
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
Crafty's {{ person['title'] }}<br /><br />
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
{{ person['blurb'] }}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -195,7 +203,11 @@
|
||||
<span class="btn btn-sm btn-info mr-2">{{ person['tags'][0] }}</span>
|
||||
{% end %}
|
||||
{% if person['tags'][1] %}
|
||||
<span class="btn btn-sm btn-primary mr-2">{{ person['tags'][1] }}</span>
|
||||
{% if type(person['tags'][1]) is list %}
|
||||
<a href="{{ person['tags'][1][1] }}" class="btn btn-sm btn-primary mr-2">{{ person['tags'][1][0] }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">{{ person['tags'][1] }}</span>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% if person['tags'][2] %}
|
||||
{% if type(person['tags'][2]) is list %}
|
||||
@ -206,11 +218,11 @@
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex align-items-start pt-3">
|
||||
<div class="wrapper align-items-start pt-3">
|
||||
{% if person['title'] %}
|
||||
Crafty's {{ person['title'] }}<br /><br />
|
||||
<h5><strong>Crafty's {{ person['title'] }}</strong></h5>
|
||||
{% end %}
|
||||
{{ person['blurb'] }}
|
||||
<p>{{ person['blurb'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -232,8 +244,8 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Patreon Supporters</h4>
|
||||
<p class="card-description"> A huge <code>thank you</code> to our Patreon supporters </p>
|
||||
<table class="table">
|
||||
<p class="card-description"> A huge <code>thank you</code> to our Patreon supporters! | <span style="color: #9365B8">Last Update: {{ data["lastUpdate"] }}</span></p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@ -241,15 +253,15 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pat in data["patreons"] %}
|
||||
{% for pat in data["patrons"] %}
|
||||
<tr>
|
||||
<td>{{ pat["name"] }}</td>
|
||||
<td>
|
||||
{% if pat["level"] == "sustainer" %}
|
||||
{% if pat["level"] == "Crafty Sustainer" %}
|
||||
<span class="btn btn-sm btn-info mr-2">Sustainer</span>
|
||||
{% elif pat["level"] == "advocate" %}
|
||||
{% elif pat["level"] == "Crafty Advocate" %}
|
||||
<span class="btn btn-sm btn-primary mr-2">Advocate</span>
|
||||
{% elif pat["level"] == "supporter" %}
|
||||
{% elif pat["level"] == "Crafty Supporter" %}
|
||||
<span class="btn btn-sm btn-inverse-success mr-2">Supporter</span>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-secondary mr-2">Other</span>
|
||||
@ -269,7 +281,7 @@
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Language Translation</h4>
|
||||
<p class="card-description"> A huge <code>thank you</code> to our community who translate! </p>
|
||||
<table class="table">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@ -310,4 +322,4 @@ $( document ).ready(function() {
|
||||
});
|
||||
</script>
|
||||
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta http-equiv="refresh" content="60">
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard') }}{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="row page-title-header">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">{{ translate('dashboard', 'dashboard') }}</h4>
|
||||
<h4 class="page-title">{{ translate('dashboard', 'dashboard', data['lang']) }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -26,59 +26,46 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host') }}</h5>
|
||||
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}</h5>
|
||||
<h3 class="mb-0 font-weight-semibold"> <i class="fas fa-chart-line"></i></h3>
|
||||
|
||||
</div>
|
||||
<div class="wrapper my-auto ml-auto ml-lg-4">
|
||||
<p id="cpu_data" class="mb-0 text-success" data-toggle="tooltip" data-placement="top" data-html="true" title="{% raw translate('dashboard', 'cpuCores') %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq') %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq') %}: {{ data.get('hosts_data').get('cpu_max_freq') }}" >
|
||||
{{ translate('dashboard', 'cpuUsage') }}: <span id="cpu_usage">{{ data.get('hosts_data').get('cpu_usage') }}</span>
|
||||
<p id="cpu_data" class="mb-0 text-success" data-toggle="tooltip" data-placement="top" data-html="true" title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}" >
|
||||
{{ translate('dashboard', 'cpuUsage', data['lang']) }}: <span id="cpu_usage">{{ data.get('hosts_data').get('cpu_usage') }}</span>
|
||||
</p>
|
||||
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top" title="{{ translate('dashboard', 'memUsage') }}: {{ data.get('hosts_data').get('mem_usage') }}" >
|
||||
{{ translate('dashboard', 'memUsage') }}: <span id="mem_percent">{{ data.get('hosts_data').get('mem_percent') }}%</span>
|
||||
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top" title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}" >
|
||||
{{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{ data.get('hosts_data').get('mem_percent') }}%</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mt-md-0 mt-4">
|
||||
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers') }}</h5>
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}</h5>
|
||||
<h3 class="mb-0 font-weight-semibold">{{ data['server_stats']['total'] }}</h3>
|
||||
|
||||
</div>
|
||||
<div class="wrapper my-auto ml-auto ml-lg-4">
|
||||
<p class="mb-0 text-success">{{ data['server_stats']['running'] }} {{ translate('dashboard', 'online').lower() }}</p>
|
||||
<p class="mb-0 text-warning"> {{ data['server_stats']['stopped'] }} {{ translate('dashboard', 'offline').lower() }}</p>
|
||||
<p class="mb-0 text-success">{{ data['server_stats']['running'] }} {{ translate('dashboard', 'online', data['lang']).lower() }}</p>
|
||||
<p class="mb-0 text-warning"> {{ data['server_stats']['stopped'] }} {{ translate('dashboard', 'offline', data['lang']).lower() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mt-md-0 mt-4">
|
||||
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players') }}</h5>
|
||||
<h3 class="mb-0 font-weight-semibold">18</h3>
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}</h5>
|
||||
<h3 class="mb-0 font-weight-semibold">{{ data['num_players'] }}</h3>
|
||||
|
||||
</div>
|
||||
<div class="wrapper my-auto ml-auto ml-lg-4">
|
||||
<p class="mb-0 text-success">35 {{ translate('dashboard', 'max') }}</p>
|
||||
<p class="mb-0 text-warning">10 {{ translate('dashboard', 'avg') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mt-md-0 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'backups') }}</h5>
|
||||
<h3 class="mb-0 font-weight-semibold">7</h3>
|
||||
</div>
|
||||
<div class="wrapper my-auto ml-auto ml-lg-4">
|
||||
<p class="mb-0 text-success">{{ translate('dashboard', 'lastBackup') }} 11-31-2020</p>
|
||||
<p class="mb-0 text-success">{{ translate('dashboard', 'nextBackup') }} 12-05-2020</p>
|
||||
<p class="mb-0 text-success">35 {{ translate('dashboard', 'max', data['lang']) }}</p>
|
||||
<p class="mb-0 text-warning">10 {{ translate('dashboard', 'avg', data['lang']) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,25 +79,34 @@
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('dashboard', 'allServers') }}</h4>
|
||||
<div class="d-md-none">
|
||||
<small>{{ translate('dashboard', 'cannotSeeOnMobile') }}<br /> {{ translate('dashboard', 'cannotSeeOnMobile2') }}</small>
|
||||
</div>
|
||||
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{ translate('dashboard', 'newServer') }}</a></div>
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('dashboard', 'allServers', data['lang']) }}</h4>
|
||||
{% if len(data['servers']) > 0 %}
|
||||
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
|
||||
{% end %}
|
||||
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> {{ translate('dashboard', 'newServer', data['lang']) }}</a></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
{% if len(data['servers']) == 0%}
|
||||
<div style="text-align: center; color: grey;">
|
||||
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
|
||||
<br>
|
||||
<h7>{{ translate('dashboard', 'no-servers', data['lang']) }} {{ translate('dashboard', 'newServer', data['lang']) }}.</h7>
|
||||
</div>
|
||||
|
||||
{% end %}
|
||||
{% if len(data['servers']) > 0 %}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>{{ translate('dashboard', 'server') }}</th>
|
||||
<th>{{ translate('dashboard', 'actions') }}</th>
|
||||
<th>{{ translate('dashboard', 'cpuUsage') }}</th>
|
||||
<th>{{ translate('dashboard', 'memUsage') }}</th>
|
||||
<th>{{ translate('dashboard', 'world') }}</th>
|
||||
<th>{{ translate('dashboard', 'players') }}</th>
|
||||
<th>{{ translate('dashboard', 'status') }}</th>
|
||||
<th>{{ translate('dashboard', 'server', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'actions', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'memUsage', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'world', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'players', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'status', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -123,16 +119,22 @@
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="actions_serverlist">
|
||||
|
||||
{% if server['stats']['running'] %}
|
||||
<a class="stop_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-stop"></i></a>
|
||||
<a class="restart_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-sync"></i></a>
|
||||
{% else %}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a>
|
||||
<td id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
|
||||
{% if server['user_command_permission'] %}
|
||||
{% if server['stats']['running'] %}
|
||||
<a class="stop_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'stop', data['lang']) }}> <i class="fas fa-stop"></i></a>
|
||||
<a class="restart_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'restart', data['lang']) }}> <i class="fas fa-sync"></i></a>
|
||||
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title={{ translate('dashboard', 'kill', data['lang']) }}> <i class="fas fa-skull"></i></a>
|
||||
{% elif server['stats']['updating']%}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating', data['lang']) }}</i></a>
|
||||
{% elif server['stats']['waiting_start']%}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="" title={{ translate('dashboard', 'delay-explained', data['lang'])}}>{{ translate('dashboard', 'starting', data['lang']) }}</i></a>
|
||||
{% else %}
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play" data-toggle="tooltip" title={{ translate('dashboard', 'start', data['lang']) }}></i></a>
|
||||
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone" data-toggle="tooltip" title={{ translate('dashboard', 'clone', data['lang']) }}></i></a>
|
||||
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title={{ translate('dashboard', 'kill', data['lang']) }}> <i class="fas fa-skull"></i></a>
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -175,10 +177,10 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if server['stats']['int_ping_results'] %}
|
||||
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max') }}<br />
|
||||
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max', data['lang']) }}<br />
|
||||
|
||||
{% if server['stats']['desc'] != 'False' %}
|
||||
{{ server['stats']['desc'] }} <br />
|
||||
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{ server['stats']['desc'] }}</span> <br />
|
||||
{% end %}
|
||||
|
||||
{% if server['stats']['version'] != 'False' %}
|
||||
@ -189,9 +191,9 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if server['stats']['running'] %}
|
||||
<i class="fas fa-thumbs-up"></i> <span class="text-success">{{ translate('dashboard', 'online') }}</span>
|
||||
<i class="fas fa-thumbs-up"></i> <span class="text-success">{{ translate('dashboard', 'online', data['lang']) }}</span>
|
||||
{% else %}
|
||||
<i class="fas fa-thumbs-down"></i> <span class="text-danger">{{ translate('dashboard', 'offline') }}</span>
|
||||
<i class="fas fa-thumbs-down"></i> <span class="text-danger">{{ translate('dashboard', 'offline', data['lang']) }}</span>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -199,6 +201,7 @@
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -206,15 +209,46 @@
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
<style>
|
||||
.popover-body{
|
||||
color: white !important;;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
|
||||
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
|
||||
for (element of all_motds) {
|
||||
initParser(element.id, element.id);
|
||||
};
|
||||
});
|
||||
$(window).ready(function(){
|
||||
$('body').click(function(){
|
||||
$('.too_small').popover("hide");
|
||||
});
|
||||
});
|
||||
$(window).resize(function() {
|
||||
// This will execute whenever the window is resized
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
else{
|
||||
$('.too_small').popover("hide");
|
||||
} // New width
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
|
||||
function send_command (server_id, command){
|
||||
@ -228,7 +262,30 @@ function send_command (server_id, command){
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){ location.reload(); }, 10000);
|
||||
setTimeout(function(){
|
||||
if (command != 'start_server'){
|
||||
location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function send_kill (server_id){
|
||||
/* this getCookie function is in base.html */
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/kill?id=' + server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){
|
||||
location.reload();
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
@ -242,8 +299,8 @@ $( document ).ready(function() {
|
||||
send_command(server_id, 'start_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand") %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStart") %} </div>'
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStart", data['lang']) %} </div>'
|
||||
});
|
||||
});
|
||||
|
||||
@ -253,8 +310,8 @@ $( document ).ready(function() {
|
||||
send_command(server_id, 'stop_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand") %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStop") %} </div>'
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientStop", data['lang']) %} </div>'
|
||||
});
|
||||
});
|
||||
|
||||
@ -263,9 +320,40 @@ $( document ).ready(function() {
|
||||
send_command(server_id, 'restart_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand") %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientRestart") %} </div>'
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientRestart", data['lang']) %} </div>'
|
||||
});
|
||||
});
|
||||
$( ".kill_button" ).click(function() {
|
||||
server_id = $(this).attr("data-id");
|
||||
bootbox.confirm({
|
||||
message: "This will kill the server process and all it's subprocesses. Killing a process can potentially corrupt files. Only do this in extreme circumstances. Are you sure you would like to continue?",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: '{% raw translate("dashboard", "kill", data['lang']) %}',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: '{% raw translate("panelConfig", "cancel", data['lang']) %}',
|
||||
className: 'btn-secondary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if(result){
|
||||
send_kill(server_id);
|
||||
var dialog = bootbox.dialog({
|
||||
title: '{% raw translate("dashboard", "killing", data['lang']) %}',
|
||||
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
|
||||
});
|
||||
|
||||
dialog.init(function(){
|
||||
setTimeout(function(){
|
||||
location.reload();
|
||||
}, 15000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (webSocket) {
|
||||
cpu_data = document.getElementById('cpu_data');
|
||||
@ -274,21 +362,43 @@ $( document ).ready(function() {
|
||||
mem_percent = document.getElementById('mem_percent');
|
||||
|
||||
webSocket.on('update_host_stats', function (hostStats) {
|
||||
var cpuDataTitle = `{% raw translate('dashboard', 'cpuCores') %}: ${hostStats.cpu_cores} <br /> {% raw translate("dashboard", "cpuCurFreq") %}: ${hostStats.cpu_cur_freq} <br /> {% raw translate("dashboard", "cpuMaxFreq") %}: ${hostStats.cpu_max_freq}`;
|
||||
var cpuDataTitle = `{% raw translate('dashboard', 'cpuCores', data['lang']) %}: ${hostStats.cpu_cores} <br /> {% raw translate("dashboard", "cpuCurFreq", data['lang']) %}: ${hostStats.cpu_cur_freq} <br /> {% raw translate("dashboard", "cpuMaxFreq", data['lang']) %}: ${hostStats.cpu_max_freq}`;
|
||||
cpu_data.setAttribute('data-original-title', cpuDataTitle);
|
||||
cpu_usage.textContent = hostStats.cpu_usage;
|
||||
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage") %}: ${hostStats.mem_usage}`);
|
||||
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
|
||||
mem_percent.textContent = hostStats.mem_percent + '%';
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('send_start_reload', function (start_error) {
|
||||
location.reload()
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
var id = 'controls';
|
||||
var dataId = updateButton.server_id;
|
||||
var string = updateButton.string
|
||||
var id = id.concat(updateButton.server_id);
|
||||
if (updateButton.isUpdating){
|
||||
console.log(updateButton.isUpdating)
|
||||
document.getElementById(id).innerHTML = string;
|
||||
}
|
||||
else{
|
||||
window.location.reload()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$( ".clone_button" ).click(function() {
|
||||
server_id = $(this).attr("data-id");
|
||||
send_command(server_id, 'clone_server');
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("dashboard", "sendingCommand") %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientClone") %} </div>'
|
||||
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("dashboard", "bePatientClone", data['lang']) %} </div>'
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -32,11 +32,11 @@
|
||||
<div class="col-sm-12 grid-margin stretch-card">
|
||||
<div class="card card-statistics social-card google-card card-colored">
|
||||
<div class="card-body">
|
||||
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('accessDenied', 'accessDenied') }}</h4>
|
||||
<h5 class="headline font-weight-medium">{{ translate('accessDenied', 'noAccess') }}</h5>
|
||||
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('accessDenied', 'accessDenied', data['lang']) }}</h4>
|
||||
<h5 class="headline font-weight-medium">{{ translate('accessDenied', 'noAccess', data['lang']) }}</h5>
|
||||
<p class="mb-2 comment font-weight-light">
|
||||
{{ translate('accessDenied', 'contactAdmin') }}<br /><br />
|
||||
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('accessDenied', 'contact') }}</a>
|
||||
{{ translate('accessDenied', 'contactAdmin', data['lang']) }}<br /><br />
|
||||
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('accessDenied', 'contact', data['lang']) }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,28 +30,28 @@
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> Users</h4>
|
||||
<div class="d-md-none">
|
||||
<small>Can't see everything on mobile?<br /> Try scrolling the table sideways.</small>
|
||||
</div>
|
||||
<h4 class="card-title"><i class="fas fa-users"></i> Users</h4>
|
||||
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
|
||||
|
||||
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> Add New User</a></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>User</th>
|
||||
<th>Enabled</th>
|
||||
<th>API Token</th>
|
||||
<th>Allowed Servers</th>
|
||||
<th>Assigned Roles</th>
|
||||
<th>Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in data['users'] %}
|
||||
<tr>
|
||||
<td>{{ user.username }}</td>
|
||||
<td><i class="fas fa-user"></i> {{ user.username }}</td>
|
||||
<td>
|
||||
{% if user.enabled %}
|
||||
<span class="text-success">
|
||||
@ -63,14 +63,27 @@
|
||||
</span>
|
||||
|
||||
{% end %}
|
||||
|
||||
</td>
|
||||
<td>{{ user.api_token }}</td>
|
||||
<td>{{ [] }}</td>
|
||||
<td>
|
||||
<button data-toggle="tooltip" title="Show API Key" data-id="{{ user.api_token }}" type="button" class="btn btn-info show_button">Show</button>
|
||||
</td>
|
||||
<td id="server_list_{{user.user_id}}">
|
||||
<ul id="{{user.user_id}}">
|
||||
{% for item in data['auth-servers'][user.user_id] %}
|
||||
<li>{{item}}</li>
|
||||
{% end %}
|
||||
</ul>
|
||||
</td>
|
||||
<td id="role_list_{{user.user_id}}">
|
||||
<ul>
|
||||
{% for item in data['user-roles'][user.user_id] %}
|
||||
<li data-toggle="tooltip" title="{{ item }}">{{item}}</li>
|
||||
{% end %}
|
||||
</ul>
|
||||
</td>
|
||||
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -82,19 +95,18 @@
|
||||
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> Roles</h4>
|
||||
<div class="d-md-none">
|
||||
<small>Can't see everything on mobile?<br /> Try scrolling the table sideways.</small>
|
||||
</div>
|
||||
<h4 class="card-title"><i class="fas fa-user-tag"></i> Roles</h4>
|
||||
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
|
||||
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> Add New Role</a></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Role</th>
|
||||
<th>Allowed Servers</th>
|
||||
<th>Role Users</th>
|
||||
<th>Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -102,7 +114,23 @@
|
||||
{% for role in data['roles'] %}
|
||||
<tr>
|
||||
<td>{{ role.role_name }}</td>
|
||||
<td>{{ [] }}</td>
|
||||
<td id="role_list_{{role.role_id}}">
|
||||
<ul id="{{role.role_id}}">
|
||||
{% for item in data['role-servers'][role.role_id] %}
|
||||
<li>{{item}}</li>
|
||||
{% end %}
|
||||
</ul>
|
||||
</td>
|
||||
<td><ul>
|
||||
{% for user in data['users'] %}
|
||||
{% for ruser in data['user-roles'][user.user_id] %}
|
||||
{% if ruser == role.role_name %}
|
||||
<li>{{ user.username }}</li>
|
||||
{% end %}
|
||||
{% end %}
|
||||
{% end %}
|
||||
</ul>
|
||||
</td>
|
||||
<td><a href="/panel/edit_role?id={{role.role_id}}"><i class="fas fa-pencil-alt"></i></a></td>
|
||||
</tr>
|
||||
{% end %}
|
||||
@ -121,17 +149,64 @@
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.popover-body{
|
||||
color: white !important;;
|
||||
}
|
||||
</style>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
$('.too_small2').popover("show");
|
||||
}
|
||||
|
||||
});
|
||||
$(window).ready(function(){
|
||||
$('body').click(function(){
|
||||
$('.too_small').popover("hide");
|
||||
$('.too_small2').popover("hide");
|
||||
});
|
||||
});
|
||||
$(window).resize(function() {
|
||||
// This will execute whenever the window is resized
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small').popover("show");
|
||||
}
|
||||
else{
|
||||
$('.too_small').popover("hide");
|
||||
} // New width
|
||||
if($(window).width() < 1000){
|
||||
$('.too_small2').popover("show");
|
||||
}
|
||||
else{
|
||||
$('.too_small2').popover("hide");
|
||||
} // New width
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
|
||||
$( document ).ready(function() {
|
||||
console.log('ready for JS!')
|
||||
|
||||
});
|
||||
|
||||
$( ".show_button" ).click(function() {
|
||||
console.log("showing key");
|
||||
api_key = $(this).attr("data-id");
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '',
|
||||
message: api_key,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% end %}
|
@ -40,15 +40,14 @@
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_name'] }}&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>Config</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_name'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<!-- <li class="nav-item">
|
||||
<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>
|
||||
</li>
|
||||
</li> -->
|
||||
</ul>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
{% if data['new_role'] %}
|
||||
@ -59,73 +58,153 @@
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['role']['role_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="role_name">Role Name <small class="text-muted ml-1"> - What you wish to call this role</small> </label>
|
||||
<input type="text" class="form-control" name="role_name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" >
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-user-tag"></i> Role Settings</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="role_name">Role Name <small class="text-muted ml-1"> - What you wish to call this role</small> </label>
|
||||
<input type="text" class="form-control" name="role_name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-server"></i> Allowed Servers <small class="text-muted ml-1"> - servers this role is allowed to access </small> </h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Server Name</th>
|
||||
<th>Access?</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 %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_membership">Servers <small class="text-muted ml-1"> - servers this role is allowed to access </small> </label>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Server Name</th>
|
||||
<th>Access?</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="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="1">
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" 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> Roles Permissions <small class="text-muted ml-1"> - permissions this role has on this/these servers </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>Permission Name</th>
|
||||
<th>Authorized ?</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>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2">Save</button>
|
||||
<button type="reset" class="btn btn-light">Cancel</button>
|
||||
<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">Role Config Area</h4>
|
||||
<p class="card-description"> Here is where you can change the configuration of your role</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
Created: {{ str(data['role']['created']) }}
|
||||
<br />
|
||||
Last updated: {{ str(data['role']['last_update']) }}
|
||||
<br />
|
||||
</p>
|
||||
</blockquote>
|
||||
</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> Users Assigned to Role:</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>User Name</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 class="text-center">
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Role Config Area</h4>
|
||||
<p class="card-description"> Here is where you can change the configuration of your role</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
Created: {{ str(data['role']['created']) }}
|
||||
<br />
|
||||
Last updated: {{ str(data['role']['last_update']) }}
|
||||
<br />
|
||||
</p>
|
||||
</blockquote>
|
||||
<div class="text-center">
|
||||
{% if data['new_role'] %}
|
||||
<a class="btn btn-sm btn-danger disabled">Delete Role</a><br />
|
||||
<small>You cannot delete something that does not yet exist</small>
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete Role</a><br />
|
||||
<small>You cannot delete something that does not yet exist</small>
|
||||
{% else %}
|
||||
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger">Delete Role</a>
|
||||
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i> Delete Role</a>
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,97 +40,127 @@
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<a class="nav-link active" href="/panel/{{ 'add_user' if data['new_user'] else 'edit_user' }}?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true">
|
||||
<i class="fas fa-cogs"></i>Config</a>
|
||||
</li>
|
||||
{% if not data['new_user'] %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<a class="nav-link" href="/panel/add_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
|
||||
<i class="fas fa-folder-tree"></i>Other</a>
|
||||
</li>
|
||||
{% end %}
|
||||
</ul>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
{% if data['new_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 %}
|
||||
<form class="forms-sample" method="post" action="/panel/edit_user">
|
||||
<form id="user_form" class="forms-sample" method="post" action="/panel/edit_user">
|
||||
{% end %}
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">User Name <small class="text-muted ml-1"> - What you wish to call this user</small> </label>
|
||||
<input type="text" class="form-control" name="username" id="username" value="{{ data['user']['username'] }}" placeholder="User Name" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password0">Password <small class="text-muted ml-1"></small> </label>
|
||||
<input type="password" class="form-control" name="password0" id="password0" value="" placeholder="Password" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password1">Repeat Password <small class="text-muted ml-1"></small> </label>
|
||||
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="role_membership">Roles <small class="text-muted ml-1"> - the roles this user is a member of</small> </label>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Role Name</th>
|
||||
<th>Member?</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>
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-user"></i> User Settings</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="username">User Name <small class="text-muted ml-1"> - What you wish to call this user</small> </label>
|
||||
<input type="text" class="form-control" name="username" id="username" value="{{ data['user']['username'] }}" placeholder="User Name" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="password0">Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
|
||||
<input type="password" class="form-control" name="password0" id="password0" value="" placeholder="Password" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="password1">Repeat Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
|
||||
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="language">User Language:</label>
|
||||
<select class="form-select" id="language" name="language" form="user_form">
|
||||
{% for lang in data['languages'] %}
|
||||
<option value="{{lang}}">{{lang}}</option>
|
||||
{% end %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_membership">Servers <small class="text-muted ml-1"> - servers this user is allowed to access </small> </label>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Server Name</th>
|
||||
<th>Access?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for server in data['servers_all'] %}
|
||||
<tr>
|
||||
<td>{{ server['server_name'] }}</td>
|
||||
<td>
|
||||
{% if server['server_id'] in data['servers'] %}
|
||||
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" disabled>
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" disabled>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
{% 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> Roles <small class="text-muted ml-1"> - the roles this user is a member of</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>Role Name</th>
|
||||
<th>Member?</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 %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Put Permissions Crafty part here -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header header-sm d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title"><i class="fas fa-user-lock"></i> Crafty Permissions <small class="text-muted ml-1"> - permissions this user has on Crafty Controller </small></h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>Permission Name</th>
|
||||
<th>Authorized ?</th>
|
||||
<th>Quantity</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>
|
||||
|
||||
@ -161,15 +191,15 @@
|
||||
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2">Save</button>
|
||||
<button type="reset" class="btn btn-light">Cancel</button>
|
||||
<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">User Config Area</h4>
|
||||
<h4 class="card-title"><i class="fas fa-user-cog"></i> User Config Area</h4>
|
||||
<p class="card-description"> Here is where you can change the configuration of your user</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
@ -189,13 +219,13 @@
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% if data['new_user'] %}
|
||||
<a class="btn btn-sm btn-danger disabled">Delete User</a><br />
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete User</a><br />
|
||||
<small>You cannot delete something that does not yet exist</small>
|
||||
{% elif data['user']['superuser'] %}
|
||||
<a class="btn btn-sm btn-danger disabled">Delete User</a><br />
|
||||
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete User</a><br />
|
||||
<small>You cannot delete a superuser</small>
|
||||
{% else %}
|
||||
<a href="/panel/remove_user?id={{ data['user']['user_id'] }}" class="btn btn-sm btn-danger">Delete User</a>
|
||||
<a href="/panel/remove_user?id={{ data['user']['user_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i> Delete User</a>
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
|
@ -5,33 +5,33 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-3 mr-2">
|
||||
{% if data['server_stats']['running'] %}
|
||||
<b>{{ translate('serverStats', 'serverStatus') }}:</b> <span class="text-success">{{ translate('serverStats', 'online') }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted') }}:</b> <span id="started">{{ data['server_stats']['started'] }} ({{ translate('serverStats', 'serverTime') }})</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime') }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime') }}</span>
|
||||
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }} ({{ translate('serverStats', 'serverTime', data['lang']) }})</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }}</span>
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'serverStatus') }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline') }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted') }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline') }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime') }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline') }}</span>
|
||||
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
|
||||
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 mr-2">
|
||||
<b>{{ translate('serverStats', 'cpuUsage') }}:</b> {{ data['server_stats']['cpu'] }}% <br />
|
||||
<b>{{ translate('serverStats', 'memUsage') }}:</b> {{ data['server_stats']['mem'] }} <br />
|
||||
<b>{{ translate('serverStats', 'cpuUsage', data['lang']) }}:</b> {{ data['server_stats']['cpu'] }}% <br />
|
||||
<b>{{ translate('serverStats', 'memUsage', data['lang']) }}:</b> {{ data['server_stats']['mem'] }} <br />
|
||||
{% if data['server_stats']['int_ping_results'] %}
|
||||
<b>{{ translate('serverStats', 'players') }}:</b> {{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}<br />
|
||||
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> {{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}<br />
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'players') }}:</b> 0/0<br />
|
||||
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> 0/0<br />
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 mr-2">
|
||||
{% if data['server_stats']['version'] != 'False' %}
|
||||
<b>{{ translate('serverStats', 'version') }}:</b> {{ data['server_stats']['version'] }} <br />
|
||||
<b>{{ translate('serverStats', 'description') }}:</b> {{ data['server_stats']['desc'] }} <br />
|
||||
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ data['server_stats']['version'] }} <br />
|
||||
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ data['server_stats']['desc'] }}</span> <br />
|
||||
{% else %}
|
||||
<b>{{ translate('serverStats', 'version') }}:</b> {{ translate('serverStats', 'unableToConnect') }} <br />
|
||||
<b>{{ translate('serverStats', 'description') }}:</b> {{ translate('serverStats', 'unableToConnect') }} <br />
|
||||
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
|
||||
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
|
||||
<script src="/static/assets/vendors/moment/moment.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
|
||||
function durationToHumanizedString (duration) {
|
||||
@ -86,11 +87,8 @@
|
||||
let startedLocal;
|
||||
|
||||
if (started != null) {
|
||||
console.log('88', '{{ data['server_stats']['started'] }}');
|
||||
{% if data['server_stats']['started'] != 'False' %}
|
||||
startedUTC = '{{ (datetime.datetime.strptime(data['server_stats']['started'], '%Y-%m-%d %H:%M:%S') - datetime.timedelta(seconds=-time.timezone)).strftime('%Y-%m-%d %H:%M:%S') }}';
|
||||
{% end %}
|
||||
console.log('utc', startedUTC);
|
||||
startedUTC = '{{ data['server_stats']['started'] }}';
|
||||
console.log('started utc:', startedUTC);
|
||||
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
let browserUTCOffset = moment().utcOffset(); // This is in minutes
|
||||
@ -98,34 +96,29 @@
|
||||
startedLocal = startedUTC.utcOffset(browserUTCOffset);
|
||||
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
console.log('startedLocal', startedLocal);
|
||||
console.log('startedLocalFormatted', startedLocalFormatted);
|
||||
console.log('started local time:', startedLocalFormatted);
|
||||
|
||||
started.textContent = startedLocalFormatted
|
||||
}
|
||||
|
||||
let nowServerTime = '{{ data['time'] }}';
|
||||
let startedServerTime = '{{ data['server_stats']['started'] }}';
|
||||
|
||||
if (uptime != null && started != null) {
|
||||
|
||||
var msdiff = moment(nowServerTime,"YYYY-MM-DD hh:mm:ss")
|
||||
.diff(moment(startedServerTime,"YYYY-MM-DD hh:mm:ss"));
|
||||
var calculateUptime = () => {
|
||||
var msdiff = moment()
|
||||
.diff(startedLocal);
|
||||
var diff = moment.duration(msdiff);
|
||||
|
||||
uptime.textContent = durationToHumanizedString(diff);
|
||||
}
|
||||
|
||||
if (uptime != null && started != null) {
|
||||
|
||||
console.log('startedLocal', startedLocal)
|
||||
if (startedLocal) {
|
||||
var uptimeLoop = setInterval(() => {
|
||||
var msdiff = moment()
|
||||
.diff(startedLocal);
|
||||
var diff = moment.duration(msdiff);
|
||||
|
||||
uptime.textContent = durationToHumanizedString(diff);
|
||||
}, 1000)
|
||||
calculateUptime()
|
||||
var uptimeLoop = setInterval(calculateUptime, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
initParser('input_motd', 'input_motd');
|
||||
|
||||
});
|
||||
</script>
|
44
app/frontend/templates/panel/parts/server_controls_list.html
Normal file
@ -0,0 +1,44 @@
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
{% if data['permissions']['Terminal'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% 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>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Logs'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'logs' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="false">
|
||||
<i class="fas fa-file-signature"></i>{{ translate('serverDetails', 'logs', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Schedule'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'schedule' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Backup'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
|
||||
<i class="fas fa-save"></i>{{ translate('serverDetails', 'backup', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Files'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% 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>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Config'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% 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>
|
||||
</li>
|
||||
{% end %}
|
||||
{% if data['permissions']['Players'] in data['user_permissions'] %}
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link {% if data['active_link'] == 'admin_controls' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true">
|
||||
<i class="fas fa-users"></i>{{ translate('serverDetails', 'playerControls', data['lang']) }}</a>
|
||||
</li>
|
||||
{% end %}
|
||||
</ul>
|
@ -4,7 +4,7 @@
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails') }}{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails') }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -32,38 +32,8 @@
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link active" 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') }}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<style>
|
||||
@ -95,7 +65,7 @@
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
<h2>{{ translate('serverPlayerManagement', 'players') }}:</h2>
|
||||
<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">
|
||||
@ -111,10 +81,10 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h2>{{ translate('serverPlayerManagement', 'bannedPlayers') }}:</h2>
|
||||
<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') }}</h3>
|
||||
<h3>{{ translate('serverPlayerManagement', 'loadingBannedPlayers', data['lang']) }}</h3>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails') }}{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails') }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -32,37 +32,7 @@
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="true">
|
||||
<i class="fas fa-save"></i>{{ translate('serverDetails', 'backup') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="false">
|
||||
<i class="fas fa-cogs"></i>{{ translate('serverDetails', 'config') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="false">
|
||||
<i class="fas fa-users"></i>{{ translate('serverDetails', 'playerControls') }}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
@ -72,37 +42,37 @@
|
||||
<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') }}</a>
|
||||
<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">
|
||||
<label for="server_name">{{ translate('serverBackups', 'storageLocation') }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc') }}</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') }}" >
|
||||
<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']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_path">{{ translate('serverBackups', 'maxBackups') }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups') }}" >
|
||||
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="superuser" class="form-check-label ml-4 mb-4">
|
||||
{% if data['backup_config']['auto_enabled'] %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" checked="" value="1" >{{ translate('serverBackups', 'backupAtMidnight') }}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" checked="" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" value="1" >{{ translate('serverBackups', 'backupAtMidnight') }}
|
||||
<input type="checkbox" class="form-check-input" id="auto_enabled" name="auto_enabled" value="1" >{{ translate('serverBackups', 'backupAtMidnight', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save') }}</button>
|
||||
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel') }}</button>
|
||||
<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 class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{ translate('serverBackups', 'currentBackups') }}</h4>
|
||||
<h4 class="card-title">{{ translate('serverBackups', 'currentBackups', data['lang']) }}</h4>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -111,10 +81,9 @@
|
||||
<table class="table table-responsive dataTable" id="backup_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">{{ translate('serverBackups', 'download') }}</th>
|
||||
<th>{{ translate('serverBackups', 'path') }}</th>
|
||||
<th width="20%">{{ translate('serverBackups', 'size') }}</th>
|
||||
<th width="10%">{{ translate('serverBackups', 'delete') }}</th>
|
||||
<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>
|
||||
@ -123,18 +92,17 @@
|
||||
<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') }}
|
||||
{{ translate('serverBackups', 'download', data['lang']) }}
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
<button data-file="{{ backup['path'] }}" class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'delete', data['lang']) }}
|
||||
</button>
|
||||
</td>
|
||||
<td>{{ backup['path'] }}</td>
|
||||
<td>{{ backup['size'] }}</td>
|
||||
<td>
|
||||
<button data-file="{{ backup['path'] }}" class="btn btn-danger del_button">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
{{ translate('serverBackups', 'delete') }}
|
||||
</button>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
||||
@ -169,7 +137,7 @@
|
||||
|
||||
function backup_started(time='5-10') {
|
||||
bootbox.alert({
|
||||
message: "{{ translate('serverBackups', 'backupTask') }}",
|
||||
message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}",
|
||||
backdrop: true
|
||||
});
|
||||
}
|
||||
@ -181,10 +149,13 @@
|
||||
|
||||
console.log('Sending Command to delete backup: ' + filename)
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/del_file?server_id='+id,
|
||||
data: data_to_send,
|
||||
data: {
|
||||
file_path: filename,
|
||||
id: id
|
||||
},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
},
|
||||
@ -220,20 +191,21 @@
|
||||
console.log("file to delete is" + file_to_del);
|
||||
|
||||
bootbox.confirm({
|
||||
title: "{% raw translate('serverBackups', 'destroyBackup') %}",
|
||||
message: "{{ translate('serverBackups', 'confirmDelete') }}",
|
||||
title: "{% raw translate('serverBackups', 'destroyBackup', data['lang']) %}",
|
||||
message: "{{ translate('serverBackups', 'confirmDelete', data['lang']) }}",
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel") }}'
|
||||
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
|
||||
},
|
||||
confirm: {
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm") }}'
|
||||
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
console.log(result);
|
||||
if (result == true) {
|
||||
del_backup(file_to_del, {{ data['server_stats']['server_id']['server_id'] }} );
|
||||
var full_path = '{{ data['backup_path'] }}' + '/' + file_to_del;
|
||||
del_backup(full_path, {{ data['server_stats']['server_id']['server_id'] }} );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails') }}{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails') }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -32,37 +32,7 @@
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link active" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
@ -72,97 +42,104 @@
|
||||
<input type="hidden" name="subpage" value="config">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverConfig', 'serverName') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName') }}" >
|
||||
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_path">{{ translate('serverConfig', 'serverPath') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="{{ translate('serverConfig', 'serverPath') }}" >
|
||||
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation') }}" >
|
||||
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="executable">{{ translate('serverConfig', 'serverExecutable') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable') }}" >
|
||||
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats']['server_id']['execution_command'] }}" placeholder="{{ translate('serverConfig', 'serverExecutionCommand') }}" >
|
||||
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats']['server_id']['execution_command'] }}" placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc') }}</small> </label>
|
||||
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand') }}" >
|
||||
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc') }}</small> </label>
|
||||
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverPort') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc') }}</small> </label>
|
||||
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}</small> </label>
|
||||
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverIP') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc') }}</small> </label>
|
||||
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter') }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc') }}</small> </label>
|
||||
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc', data['lang']) }}</small> </label>
|
||||
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" >
|
||||
</div>
|
||||
|
||||
<div class="form-check-flat">
|
||||
<label for="auto_start" class="form-check-label ml-4 mb-4">
|
||||
{% if data['server_stats']['server_id']['auto_start'] %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked="" value="1">{{ translate('serverConfig', 'serverAutoStart') }}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked="" value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1">{{ translate('serverConfig', 'serverAutoStart') }}
|
||||
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
<label for="crash_detection" class="form-check-label ml-4 mb-4">
|
||||
{% if data['server_stats']['server_id']['crash_detection'] %}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" checked="" value="1">{{ translate('serverConfig', 'serverCrashDetection') }}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" checked="" value="1">{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
|
||||
{% else %}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" value="1" >{{ translate('serverConfig', 'serverCrashDetection') }}
|
||||
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" value="1" >{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
|
||||
{% end %}
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success mr-2">{{ translate('serverConfig', 'save') }}</button>
|
||||
<button type="reset" class="btn btn-light">{{ translate('serverConfig', 'cancel') }}</button>
|
||||
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('serverConfig', 'save', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{ translate('serverConfigHelp', 'title') }}</h4>
|
||||
<p class="card-description"> {{ translate('serverConfigHelp', 'desc') }}</p>
|
||||
<h4 class="card-title">{{ translate('serverConfigHelp', 'title', data['lang']) }}</h4>
|
||||
<p class="card-description"> {{ translate('serverConfigHelp', 'desc', data['lang']) }}</p>
|
||||
<blockquote class="blockquote">
|
||||
<p class="mb-0">
|
||||
{% raw translate('serverConfigHelp', 'perms') %}
|
||||
{% raw translate('serverConfigHelp', 'perms', data['lang']) %}
|
||||
</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% if data['server_stats']['running'] %}
|
||||
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer') }}</a><br />
|
||||
<small>{{ translate('serverConfig', 'stopBeforeDeleting') }}</small>
|
||||
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig', 'update', data['lang']) }}</button>
|
||||
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang']) }}</a><br />
|
||||
<small>{{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }}</small>
|
||||
{% else %}
|
||||
<a href="/panel/remove_server?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer') }}</a>
|
||||
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig', 'update', data['lang']) }}</button>
|
||||
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer', data['lang']) }}</button>
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
@ -196,6 +173,134 @@
|
||||
|
||||
});
|
||||
|
||||
function deleteServerE(callback) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/delete_server?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
data: {
|
||||
},
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
function deleteServerFilesE(path, callback) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/delete_server_files?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
data: {
|
||||
},
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
|
||||
|
||||
function send_command (server_id, command){
|
||||
<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/server/command?command=' + command + '&id=' + server_id,
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){ location.reload(); }, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
if(command != "delete_server" && command != "delete_server_files"){
|
||||
bootbox.alert({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingRequest", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientUpdate", data['lang']) %} </div>'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteServer (){
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteFilesQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverConfig', 'yesDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
|
||||
className: 'btn-link',
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result){
|
||||
deleteServerE()
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
|
||||
return;}
|
||||
else{
|
||||
deleteServerFilesE();
|
||||
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
|
||||
bootbox.dialog({
|
||||
backdrop: true,
|
||||
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
|
||||
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
|
||||
closeButton: false
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
function deleteConfirm (){
|
||||
path = "{{data['server_stats']['server_id']['path']}}";
|
||||
name = "{{data['server_stats']['server_id']['server_name']}}";
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverConfig', 'deleteServerQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverConfig', 'yesDelete', data['lang']) }}",
|
||||
className: 'btn-danger',
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverConfig', 'noDelete', data['lang']) }}",
|
||||
className: 'btn-link',
|
||||
}
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result){
|
||||
return;
|
||||
return;}
|
||||
else{
|
||||
deleteServer();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails') }}{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails') }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -32,104 +32,94 @@
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link active" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<noscript>
|
||||
{{ translate('serverFiles', 'noscript') }}
|
||||
{{ translate('serverFiles', 'noscript', data['lang']) }}
|
||||
</noscript>
|
||||
<div id="files-tree-nav" class="overlay">
|
||||
|
||||
<!-- Button to close the overlay navigation -->
|
||||
<a href="javascript:void(0)" class="closebtn" onclick="document.getElementById('files-tree-nav').style.height = '0%';">×</a>
|
||||
|
||||
<!-- Overlay content -->
|
||||
<div id="files-tree-nav-content" class="overlay-content">
|
||||
<a onclick="createFileE(event)" href="javascript:void(0)" id="createFile" href="#">{{ translate('serverFiles', 'createFile') }}</a>
|
||||
<a onclick="createDirE(event)" href="javascript:void(0)" id="createDir" href="#">{{ translate('serverFiles', 'createDir') }}</a>
|
||||
<a onclick="renameItemE(event)" href="javascript:void(0)" id="renameItem" href="#">{{ translate('serverFiles', 'rename') }}</a>
|
||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#">{{ translate('serverFiles', 'delete') }}</a>
|
||||
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#">{{ translate('serverFiles', 'delete') }}</a>
|
||||
<a onclick="createFileE(event)" href="javascript:void(0)" id="createFile" href="#">{{ translate('serverFiles', 'createFile', data['lang']) }}</a>
|
||||
<a onclick="createDirE(event)" href="javascript:void(0)" id="createDir" href="#">{{ translate('serverFiles', 'createDir', data['lang']) }}</a>
|
||||
<a onclick="renameItemE(event)" href="javascript:void(0)" id="renameItem" href="#">{{ translate('serverFiles', 'rename', data['lang']) }}</a>
|
||||
<a onclick="uploadFilesE(event)" href="javascript:void(0)" id="upload" href="#">{{ translate('serverFiles', 'upload', data['lang']) }}</a>
|
||||
<a onclick="unzipFilesE(event)" href="javascript:void(0)" id="unzip" href="#">{{ translate('serverFiles', 'unzip', data['lang']) }}</a>
|
||||
<a onclick="downloadFileE(event)" href="javascript:void(0)" id="downloadFile" href="#">{{ translate('serverFiles', 'download', data['lang']) }}</a>
|
||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#" style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#" style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||
<a href="javascript:void(0)" class="closebtn" style="color: #8862e0;" onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{ translate('serverFiles', 'close', data['lang']) }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<style>
|
||||
/* The Overlay (background) */
|
||||
.overlay {
|
||||
/* Height & width depends on how you want to reveal the overlay (see JS below) */
|
||||
height: 0;
|
||||
width: 100vw;
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1031; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: rgb(0,0,0); /* Black fallback color */
|
||||
background-color: rgba(0,0,0, 0.9); /* Black w/opacity */
|
||||
overflow-x: hidden; /* Disable horizontal scroll */
|
||||
transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
|
||||
padding: 10px 0;
|
||||
z-index: 10000;
|
||||
overflow: scroll;
|
||||
|
||||
}
|
||||
.overlay::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.overlay {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
/* Position the content inside the overlay */
|
||||
.overlay-content {
|
||||
position: relative;
|
||||
top: 25%; /* 25% from the top */
|
||||
width: 100%; /* 100% width */
|
||||
text-align: center; /* Centered text/links */
|
||||
margin-top: 30px; /* 30px top margin to avoid conflict with the close button on smaller screens */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* The navigation links inside the overlay */
|
||||
.overlay a {
|
||||
padding: 8px;
|
||||
text-decoration: none;
|
||||
font-size: 36px;
|
||||
color: #818181;
|
||||
display: block; /* Display block instead of inline */
|
||||
transition: 0.3s; /* Transition effects on hover (color) */
|
||||
font: inherit;
|
||||
border: 0;
|
||||
padding: 10px 30px 10px 15px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
text-decoration: unset;
|
||||
color: #000;
|
||||
font-weight: 500;
|
||||
transition: 0.5s linear;
|
||||
-webkit-transition: 0.5s linear;
|
||||
-moz-transition: 0.5s linear;
|
||||
-ms-transition: 0.5s linear;
|
||||
-o-transition: 0.5s linear;
|
||||
}
|
||||
|
||||
/* When you mouse over the navigation links, change their color */
|
||||
.overlay a:hover, .overlay a:focus {
|
||||
color: #f1f1f1;
|
||||
background:#f1f3f7;
|
||||
color: #4b00ff;
|
||||
}
|
||||
|
||||
/* Position the close button (top right corner) */
|
||||
.overlay .closebtn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 45px;
|
||||
font-size: 60px;
|
||||
.overlay .closebtn .closebtn:hover {
|
||||
background-color: red;
|
||||
color: red;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
/* When the height of the screen is less than 450 pixels, change the font-size of the links and position the close button again, so they don't overlap */
|
||||
@ -147,10 +137,10 @@
|
||||
<div class="tree-caret tree-ctx-item files-tree-title">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
{{ translate('serverFiles', 'files') }}
|
||||
{{ translate('serverFiles', 'files', data['lang']) }}
|
||||
</div>
|
||||
<ul class="tree-nested" id="files-tree">
|
||||
<li>{{ translate('serverFiles', 'error') }}</li>
|
||||
<li>{{ translate('serverFiles', 'error', data['lang']) }}</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
@ -166,8 +156,9 @@
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Style the caret/arrow */
|
||||
.tree-caret {
|
||||
/* Style the items */
|
||||
.tree-item,
|
||||
.files-tree-title {
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
}
|
||||
@ -194,20 +185,21 @@
|
||||
}
|
||||
</style>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h2 id="fileError"></h2>
|
||||
<div id="editorParent">
|
||||
{{ translate('serverFiles', 'editingFile') }} <span id="editingFile"></span>
|
||||
{{ translate('serverFiles', 'editingFile', data['lang']) }} <span id="editingFile"></span>
|
||||
<div id="editor" onresize="editor.resize()" style="resize: both;width: 100%;">file_contents</div>
|
||||
<br/>
|
||||
</div>
|
||||
{{ translate('serverFiles', 'keybindings') }}:
|
||||
{{ translate('serverFiles', 'keybindings', data['lang']) }}:
|
||||
<div class="btn-group" role="group">
|
||||
<button onclick="setKeyboard(event.target)" class="btn btn-primary" data-handler-name="null">{{ translate('serverFiles', 'default') }}</button>
|
||||
<button onclick="setKeyboard(event.target)" class="btn btn-primary" data-handler-name="null">{{ translate('serverFiles', 'default', data['lang']) }}</button>
|
||||
<button onclick="setKeyboard(event.target)" class="btn btn-secondary" data-handler-name="ace/keyboard/vim">Vim</button>
|
||||
<button onclick="setKeyboard(event.target)" class="btn btn-secondary" data-handler-name="ace/keyboard/emacs">Emacs</button>
|
||||
<button onclick="setKeyboard(event.target)" class="btn btn-secondary" data-handler-name="ace/keyboard/sublime">Sublime</button>
|
||||
</div>
|
||||
<h3 id="file_warn"></h3>
|
||||
<button class="btn btn-success" onclick="save()">{{ translate('serverFiles', 'save') }}</button>
|
||||
<button class="btn btn-success" onclick="save()"><i class="fas fa-save"></i> {{ translate('serverFiles', 'save', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -330,6 +322,10 @@
|
||||
regex: /^properties$/,
|
||||
replaceWith: 'ace/mode/properties'
|
||||
},
|
||||
{
|
||||
regex: /^log$/,
|
||||
replaceWith: 'ace/mode/txt'
|
||||
},
|
||||
];
|
||||
|
||||
var filePath = '';
|
||||
@ -344,10 +340,13 @@
|
||||
console.log('Got File Contents From Server');
|
||||
json = JSON.parse(data)
|
||||
if (json.error) {
|
||||
$('#editorParent').toggle(false)
|
||||
$('#editorParent').toggle(false) // hide
|
||||
$('#fileError').toggle(true) // show
|
||||
$('#fileError').text("{{ translate('serverFiles', 'fileReadError', data['lang']) }}: " + json.error) // show error
|
||||
editor.blur()
|
||||
} else {
|
||||
$('#editorParent').toggle(true)
|
||||
$('#editorParent').toggle(true) // show
|
||||
$('#fileError').toggle(false) // hide
|
||||
setFileName(event.target.innerText);
|
||||
editor.session.setValue(json.content);
|
||||
}
|
||||
@ -371,12 +370,13 @@
|
||||
setMode('txt');
|
||||
document
|
||||
.querySelector('#file_warn')
|
||||
.innerText = "{% raw translate('serverFiles', 'unsupportedLanguage') %}";
|
||||
.innerText = "{% raw translate('serverFiles', 'unsupportedLanguage', data['lang']) %}";
|
||||
}
|
||||
}
|
||||
|
||||
setFileName();
|
||||
$('#editorParent').toggle(false)
|
||||
$('#editorParent').toggle(false) // show
|
||||
$('#fileError').toggle(false) // hide
|
||||
editor.blur()
|
||||
|
||||
function setMode (extension) {
|
||||
@ -392,7 +392,7 @@
|
||||
if (!aceMode.startsWith('ace/mode/')) {
|
||||
document
|
||||
.querySelector('#file_warn')
|
||||
.innerText = "{% raw translate('serverFiles', 'unsupportedLanguage') %}";
|
||||
.innerText = "{% raw translate('serverFiles', 'unsupportedLanguage', data['lang']) %}";
|
||||
} else {
|
||||
document
|
||||
.querySelector('#file_warn')
|
||||
@ -513,6 +513,141 @@
|
||||
});
|
||||
}
|
||||
|
||||
function unZip(path, callback) {
|
||||
var token = getCookie("_xsrf")
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
headers: {'X-XSRFToken': token},
|
||||
url: '/ajax/unzip_file?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
data: {
|
||||
path: path
|
||||
},
|
||||
});
|
||||
window.location.href = "/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=files"
|
||||
}
|
||||
|
||||
function sendFile(file, path, server_id, left, onProgress){
|
||||
var xmlHttpRequest = new XMLHttpRequest();
|
||||
var token = getCookie("_xsrf")
|
||||
var fileName = file.name
|
||||
var target = '/upload?server_id=' + server_id
|
||||
var mimeType = file.type
|
||||
|
||||
xmlHttpRequest.open('POST', target, true);
|
||||
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
|
||||
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
|
||||
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
|
||||
xmlHttpRequest.setRequestHeader('X-Path', path);
|
||||
xmlHttpRequest.setRequestHeader('X-Files-Left', left);
|
||||
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
||||
xmlHttpRequest.setRequestHeader('X-ServerId', "{{ data['server_stats']['server_id']['server_id'] }}");
|
||||
xmlHttpRequest.upload.addEventListener('progress', (event) =>
|
||||
onProgress(Math.floor(event.loaded / event.total * 100)), false);
|
||||
xmlHttpRequest.addEventListener('load', (event) => {
|
||||
if (event.target.responseText == 'success') {
|
||||
console.log('Upload for file', file.name, 'was successful!')
|
||||
}
|
||||
else {
|
||||
alert('Upload failed with response: ' + event.target.responseText);
|
||||
doUpload = false;
|
||||
}
|
||||
}, false);
|
||||
xmlHttpRequest.addEventListener('error', (e) => {
|
||||
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
||||
}, false);
|
||||
|
||||
xmlHttpRequest.send(file);
|
||||
}
|
||||
|
||||
var uploadWaitDialog;
|
||||
var doUpload = true;
|
||||
|
||||
function uploadFilesE(event){
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
console.log("PATH: " + path);
|
||||
$(function () {
|
||||
server_id = {{ data['server_stats']['server_id']['server_id'] }};
|
||||
var uploadHtml = "<div>" +
|
||||
'<form id="upload_file" enctype="multipart/form-data">'+"<label class='upload-area' style='width:100%;text-align:center;' for='files'>" +
|
||||
"<input id='files' name='files' type='file' style='display:none;' multiple='true'>" +
|
||||
"<i class='fa fa-cloud-upload fa-3x'></i>" +
|
||||
"<br />" +
|
||||
"{{translate('serverFiles', 'clickUpload', data['lang'])}}" +
|
||||
"</label></form>" +
|
||||
"<br />" +
|
||||
"<ul style='margin-left:5px !important;' id='fileList'></ul>" +
|
||||
"</div><div class='clearfix'></div>";
|
||||
bootbox.dialog({
|
||||
message: uploadHtml,
|
||||
title: "{{ translate('serverFiles', 'uploadTitle', data['lang'])}}"+path,
|
||||
buttons: {
|
||||
success: {
|
||||
label: "{{ translate('serverFiles', 'upload', data['lang']) }}",
|
||||
className: "btn-default",
|
||||
callback: async function () {
|
||||
var height = files.files.length*50;
|
||||
|
||||
var waitMessage = '<p class="text-center mb-0">'+
|
||||
'<i class="fa fa-spin fa-cog"></i>'+
|
||||
"{{ translate('serverFiles', 'waitUpload', data['lang']) }}"+'<br>'+
|
||||
'<strong>'+"{{ translate('serverFiles', 'stayHere', data['lang']) }}"+'</strong>'+
|
||||
'</p>'+
|
||||
'<div class="progress" id="upload-progress-bar-parent" style="height:'+height +'px; width:100%; display: block;">'+
|
||||
'</div>'
|
||||
files = document.getElementById("files");
|
||||
uploadWaitDialog = bootbox.dialog({
|
||||
message: waitMessage,
|
||||
closeButton: false
|
||||
});
|
||||
let nFiles = files.files.length;
|
||||
for(i=0; i < files.files.length; i++) {
|
||||
if (!doUpload) {
|
||||
doUpload = true;
|
||||
hideUploadBox();
|
||||
break;
|
||||
}
|
||||
console.log(files.files[i].name);
|
||||
const progressHtml = `
|
||||
<div style="width: 100%; min-width: 100%;">
|
||||
${files.files[i].name}:
|
||||
<br><div
|
||||
id="upload-progress-bar-${i + 1}"
|
||||
class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar"
|
||||
style="width: 100%; height: 10px;"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div><br>
|
||||
`;
|
||||
$('#upload-progress-bar-parent').append(progressHtml);
|
||||
console.log(files.files.length)
|
||||
sendFile(files.files[i], path, server_id, files.files.length - i - 1, (progress) => {
|
||||
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
|
||||
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%')
|
||||
});
|
||||
}
|
||||
hideUploadBox();
|
||||
//$('#upload_file').submit(); //.trigger('submit');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
var fileList = document.getElementById("files");
|
||||
fileList.addEventListener("change", function (e) {
|
||||
var list = "";
|
||||
for (var i = 0; i < this.files.length; i++) {
|
||||
list += "<li class='col-xs-12 file-list'>" + this.files[i].name + "</li>"
|
||||
}
|
||||
|
||||
document.getElementById("fileList").innerHTML = list;
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
function getTreeView() {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
@ -566,9 +701,11 @@
|
||||
$('#createFile').toggle(isDir);
|
||||
$('#createDir').toggle(isDir);
|
||||
$('#deleteDir').toggle(isDir);
|
||||
$('#upload').toggle(isDir);
|
||||
|
||||
var isFile = event.target.classList.contains('tree-file');
|
||||
$('#deleteFile').toggle(isFile);
|
||||
$('#downloadFile').toggle(isFile);
|
||||
console.log({ 'event.target': event.target, isDir, isFile });
|
||||
|
||||
if(event.target.classList.contains('files-tree-title')) {
|
||||
@ -577,58 +714,109 @@
|
||||
$('#renameItem').hide();
|
||||
$('#deleteDir').hide();
|
||||
$('#deleteFile').hide();
|
||||
$('#downloadFile').hide();
|
||||
$('#upload').show();
|
||||
}
|
||||
if(event.target.textContent.endsWith('.zip')){
|
||||
$('#unzip').show();
|
||||
console.log(event.target.textContent)
|
||||
}else{
|
||||
$('#unzip').hide();}
|
||||
|
||||
var clientX = event.clientX;
|
||||
var clientY = event.clientY;
|
||||
|
||||
|
||||
|
||||
document.getElementById('files-tree-nav-content')
|
||||
.setAttribute('data-path', ctxmenuPath);
|
||||
|
||||
document.getElementById('files-tree-nav-content')
|
||||
.setAttribute('data-name', ctxmenuName);
|
||||
|
||||
document.getElementById("files-tree-nav").style.height = "100%";
|
||||
document.getElementById("files-tree-nav").style.display = "flex";
|
||||
document.getElementById("files-tree-nav").style.position = "fixed";
|
||||
domRect = document.getElementById("files-tree-nav").getBoundingClientRect();
|
||||
sum = (clientY+domRect['height']) - window.innerHeight
|
||||
if(domRect['height']+clientY > window.innerHeight){
|
||||
clientY = clientY - sum
|
||||
}
|
||||
document.getElementById("files-tree-nav").style.top = clientY + 'px';
|
||||
document.getElementById("files-tree-nav").style.left = clientX + 'px';
|
||||
console.log(domRect)
|
||||
console.log(window.innerHeight)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(e){
|
||||
let inside = (e.target.closest('#files-tree-nav'));
|
||||
let contextMenu = document.getElementById('files-tree-nav');
|
||||
if(!inside){
|
||||
contextMenu.setAttribute('style', 'display:none');
|
||||
}else{
|
||||
contextMenu.setAttribute('style', 'display:none');
|
||||
}
|
||||
});
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('close_upload_box', function (close_upload_box) {
|
||||
hideUploadBox();
|
||||
});
|
||||
}
|
||||
function hideUploadBox(){
|
||||
if (!uploadWaitDialog) return;
|
||||
uploadWaitDialog.modal('hide');
|
||||
getTreeView();
|
||||
}
|
||||
|
||||
function createFileE(event) {
|
||||
bootbox.prompt("{% raw translate('serverFiles', 'createFileQuestion') %}", function(result) {
|
||||
bootbox.prompt("{% raw translate('serverFiles', 'createFileQuestion', data['lang']) %}", function(result) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
name = event.target.parentElement.getAttribute('data-name');
|
||||
if (!result) return;
|
||||
|
||||
createFile(path, result, function () {
|
||||
getTreeView()
|
||||
document.getElementById('files-tree-nav').style.height = '0%';
|
||||
document.getElementById('files-tree-nav').style.display = 'none';
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function createDirE(event) {
|
||||
bootbox.prompt("{% raw translate('serverFiles', 'createDirQuestion') %}", function(result) {
|
||||
bootbox.prompt("{% raw translate('serverFiles', 'createDirQuestion', data['lang']) %}", function(result) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
name = event.target.parentElement.getAttribute('data-name');
|
||||
if (!result) return;
|
||||
|
||||
createDir(path, result, function () {
|
||||
getTreeView()
|
||||
document.getElementById('files-tree-nav').style.height = '0%';
|
||||
document.getElementById('files-tree-nav').style.display = 'none';
|
||||
});
|
||||
})
|
||||
}
|
||||
function downloadFileE(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
name = event.target.parentElement.getAttribute('data-name');
|
||||
window.location.href = '/panel/download_file?id={{ data['server_stats']['server_id']['server_id'] }}&path='+path+'&name='+name;
|
||||
}
|
||||
|
||||
function renameItemE(event) {
|
||||
bootbox.prompt("{% raw translate('serverFiles', 'renameItemQuestion') %}", function(result) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
name = event.target.parentElement.getAttribute('data-name');
|
||||
bootbox.prompt({
|
||||
title: "{% raw translate('serverFiles', 'renameItemQuestion', data['lang']) %}",
|
||||
value: name,
|
||||
callback: function(result) {
|
||||
if (!result) return;
|
||||
renameItem(path, result, function () {
|
||||
getTreeView()
|
||||
document.getElementById('files-tree-nav').style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function unzipFilesE(event) {
|
||||
path = event.target.parentElement.getAttribute('data-path');
|
||||
name = event.target.parentElement.getAttribute('data-name');
|
||||
if (!result) return;
|
||||
|
||||
renameItem(path, result, function () {
|
||||
getTreeView()
|
||||
document.getElementById('files-tree-nav').style.height = '0%';
|
||||
});
|
||||
})
|
||||
unZip(path)
|
||||
}
|
||||
|
||||
function deleteFileE(event) {
|
||||
@ -636,16 +824,16 @@
|
||||
name = event.target.parentElement.getAttribute('data-name');
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverFiles', 'deleteItemQuestion') %}",
|
||||
title: "{% raw translate('serverFiles', 'deleteItemQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverFiles', 'deleteItemQuestionMessage') %}",
|
||||
message: "{% raw translate('serverFiles', 'deleteItemQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverFiles', 'yesDelete') }}",
|
||||
label: "{{ translate('serverFiles', 'yesDelete', data['lang']) }}",
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverFiles', 'noDelete') }}",
|
||||
label: "{{ translate('serverFiles', 'noDelete', data['lang']) }}",
|
||||
className: 'btn-link'
|
||||
}
|
||||
},
|
||||
@ -653,7 +841,7 @@
|
||||
if (!result) return;
|
||||
deleteFile(path, function () {
|
||||
getTreeView()
|
||||
document.getElementById('files-tree-nav').style.height = '0%';
|
||||
document.getElementById('files-tree-nav').style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -664,16 +852,16 @@
|
||||
name = event.target.parentElement.getAttribute('data-name');
|
||||
bootbox.confirm({
|
||||
size: "",
|
||||
title: "{% raw translate('serverFiles', 'deleteItemQuestion') %}",
|
||||
title: "{% raw translate('serverFiles', 'deleteItemQuestion', data['lang']) %}",
|
||||
closeButton: false,
|
||||
message: "{% raw translate('serverFiles', 'deleteItemQuestionMessage') %}",
|
||||
message: "{% raw translate('serverFiles', 'deleteItemQuestionMessage', data['lang']) %}",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: "{{ translate('serverFiles', 'yesDelete') }}",
|
||||
label: "{{ translate('serverFiles', 'yesDelete', data['lang']) }}",
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: "{{ translate('serverFiles', 'noDelete') }}",
|
||||
label: "{{ translate('serverFiles', 'noDelete', data['lang']) }}",
|
||||
className: 'btn-link'
|
||||
}
|
||||
},
|
||||
@ -681,7 +869,7 @@
|
||||
if (!result) return;
|
||||
deleteDir(path, function () {
|
||||
getTreeView()
|
||||
document.getElementById('files-tree-nav').style.height = '0%';
|
||||
document.getElementById('files-tree-nav').style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails') }}{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails') }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -32,38 +32,7 @@
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="false">
|
||||
<i class="fas fa-terminal"></i>{{ translate('serverDetails', 'terminal') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="true">
|
||||
<i class="fas fa-file-signature"></i>{{ translate('serverDetails', 'logs') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="false">
|
||||
<i class="fas fa-cogs"></i>{{ translate('serverDetails', 'config') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="input-group">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<!-- <meta http-equiv="refresh" content="60">-->
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails') }}{% end %}
|
||||
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h4 class="page-title">
|
||||
{{ translate('serverDetails', 'serverDetails') }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
|
||||
<br />
|
||||
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
||||
</h4>
|
||||
@ -32,44 +32,8 @@
|
||||
<div class="col-sm-12 grid-margin">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
|
||||
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="true">
|
||||
<i class="fas fa-file-signature"></i>{{ translate('serverDetails', 'terminal') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
|
||||
<i class="fas fa-clock"></i>{{ translate('serverDetails', 'schedule') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="false">
|
||||
<i class="fas fa-cogs"></i>{{ translate('serverDetails', 'config') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<a class="nav-link" 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') }}</a>
|
||||
</li>
|
||||
<li class="nav-item term-nav-item">
|
||||
<label class="p-0 m-0">
|
||||
<input type="checkbox" name="stop_scroll" id="stop_scroll" />
|
||||
{{ translate('serverTerm', 'stopRefresh') }}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% include "parts/server_controls_list.html %}
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="input-group">
|
||||
<div id="virt_console" class="" style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid #383e5d; background-color:#2a2c44;height:500px; overflow: scroll;"></div>
|
||||
@ -77,31 +41,52 @@
|
||||
<br />
|
||||
|
||||
<div style="gap: 0.5rem;" class="input-group flex-wrap">
|
||||
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput') }}" autofocus="">
|
||||
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" autofocus="">
|
||||
<span class="input-group-btn ml-5">
|
||||
<input type="hidden" value="" id="last_command"/>
|
||||
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand') }}</button>
|
||||
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand', data['lang']) }}</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0">
|
||||
<button onclick="send_command(server_id, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start') }}</button>
|
||||
<button onclick="send_command(server_id, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart') %}</button>
|
||||
<button onclick="send_command(server_id, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop') }}</button>
|
||||
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
||||
{% if data['server_stats']['updating']%}
|
||||
<div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'updating', data['lang']) }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
|
||||
|
||||
{% elif data['waiting_start'] %}
|
||||
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', 'starting', data['lang']) }}</button>
|
||||
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
|
||||
<button onclick="send_command(server_id, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
|
||||
<button onclick="send_command(server_id, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
|
||||
<button onclick="send_command(server_id, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
|
||||
</div>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<style>
|
||||
#virt_console::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
#virt_console {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
</style>
|
||||
<!-- content-wrapper ends -->
|
||||
|
||||
{% end %}
|
||||
@ -110,6 +95,16 @@
|
||||
<script>
|
||||
|
||||
function send_command (server_id, command){
|
||||
if (command == 'start_server'){
|
||||
startBtn.setAttribute('disabled', 'disabled');
|
||||
restartBtn.removeAttribute('disabled');
|
||||
stopBtn.removeAttribute('disabled');
|
||||
}
|
||||
if (command == 'stop_server'){
|
||||
startBtn.removeAttribute('disabled');
|
||||
restartBtn.setAttribute('disabled', 'disabled');
|
||||
stopBtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
<!-- this getCookie function is in base.html-->
|
||||
var token = getCookie("_xsrf");
|
||||
|
||||
@ -120,12 +115,27 @@
|
||||
success: function(data){
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
setTimeout(function(){ location.reload(); }, 10000);
|
||||
setTimeout(function(){
|
||||
if (command != 'start_server'){
|
||||
location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (webSocket) {
|
||||
webSocket.on('update_button_status', function (updateButton) {
|
||||
if (updateButton.isUpdating){
|
||||
console.log(updateButton.isUpdating)
|
||||
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data['lang']) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
|
||||
}
|
||||
else{
|
||||
window.location.reload()
|
||||
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data['lang']) }}</button><button onclick="send_command(server_id, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
|
||||
}
|
||||
});
|
||||
}
|
||||
// Convert running to lower case (example: 'True' converts to 'true') and
|
||||
// then to boolean via JSON.parse()
|
||||
let online = JSON.parse('{{ data['server_stats']['running'] }}'.toLowerCase());
|
||||
@ -134,6 +144,7 @@
|
||||
let restartBtn = document.querySelector('#restart-btn');
|
||||
let stopBtn = document.querySelector('#stop-btn');
|
||||
|
||||
{% if data['permissions']['Commands'] in data['user_permissions'] %}
|
||||
if (online) {
|
||||
startBtn.setAttribute('disabled', 'disabled');
|
||||
restartBtn.removeAttribute('disabled');
|
||||
@ -143,11 +154,11 @@
|
||||
restartBtn.setAttribute('disabled', 'disabled');
|
||||
stopBtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
{% end %}
|
||||
|
||||
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
|
||||
|
||||
function get_server_log(){
|
||||
if( !$("#stop_scroll").is(':checked')){
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/ajax/server_log?id={{ data['server_stats']['server_id']['server_id'] }}',
|
||||
@ -155,10 +166,20 @@
|
||||
success: function (data) {
|
||||
console.log('Got Log From Server')
|
||||
$('#virt_console').html(data);
|
||||
scroll();
|
||||
},
|
||||
scrollConsole();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function new_line_handler(data) {
|
||||
$('#virt_console').append(data.line)
|
||||
const elem = document.getElementById('virt_console');
|
||||
const scrollDiff = (elem.scrollHeight - elem.scrollTop) - elem.clientHeight;
|
||||
if (!$("#stop_scroll").is(':checked') && scrollDiff < 450) {
|
||||
scrollConsole()
|
||||
}
|
||||
}
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
@ -171,9 +192,7 @@
|
||||
console.log( "ready!" );
|
||||
get_server_log()
|
||||
|
||||
setInterval(function(){
|
||||
get_server_log() // this will run after every 5 seconds
|
||||
}, 1500);
|
||||
webSocket.on('vterm_new_line', new_line_handler)
|
||||
});
|
||||
|
||||
$('#server_command').on('keydown', function (e) {
|
||||
@ -196,7 +215,7 @@
|
||||
|
||||
});
|
||||
|
||||
function scroll(){
|
||||
function scrollConsole(){
|
||||
var logview = $('#virt_console');
|
||||
if(logview.length)
|
||||
logview.scrollTop(logview[0].scrollHeight - logview.height());
|
||||
|
@ -33,11 +33,11 @@
|
||||
<div class="card card-statistics social-card facebook-card card-colored">
|
||||
<div class="card-body">
|
||||
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">404</h4>
|
||||
<h5 class="headline font-weight-medium">{{ translate('404', 'notFound') }}</h5>
|
||||
<h5 class="headline font-weight-medium">{{ translate('404', 'notFound', data['lang']) }}</h5>
|
||||
<p class="mb-2 comment font-weight-light">
|
||||
{{ translate('404', 'unableToFind') }}
|
||||
{{ translate('404', 'unableToFind', data['lang']) }}
|
||||
<br /><br />
|
||||
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('404', 'contact') }}</a>
|
||||
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('404', 'contact', data['lang']) }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -32,15 +32,15 @@
|
||||
<div class="col-sm-12 grid-margin stretch-card">
|
||||
<div class="card card-statistics social-card google-card card-colored">
|
||||
<div class="card-body">
|
||||
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('error', 'error') }}</h4>
|
||||
<h5 class="headline font-weight-medium">{{ translate('error', 'terribleFailure') }}</h5>
|
||||
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('error', 'error', data['lang']) }}</h4>
|
||||
<h5 class="headline font-weight-medium">{{ translate('error', 'terribleFailure', data['lang']) }}</h5>
|
||||
<p class="mb-2 comment font-weight-light">
|
||||
{{ translate('error', 'embarassing') }}<br />
|
||||
{{ translate('error', 'embarassing', data['lang']) }}<br />
|
||||
<br />
|
||||
<b>{{ translate('error', 'hereIsTheError') }}: {{data['error']}}</b><br /><br />
|
||||
<b>{{ translate('error', 'hereIsTheError', data['lang']) }}: {{data['error']}}</b><br /><br />
|
||||
That's all the help I can give you - Godspeed
|
||||
<br /><br />
|
||||
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('error', 'contact') }}</a>
|
||||
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('error', 'contact', data['lang']) }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,25 +57,25 @@
|
||||
<form action="/public/login" method="post">
|
||||
{% raw xsrf_form_html() %}
|
||||
<div class="form-group">
|
||||
<label class="label">{{ translate('login', 'username') }}</label>
|
||||
<label class="label">{{ translate('login', 'username', data['lang']) }}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control login-text-input login-input" placeholder="{{ translate('login', 'username') }}" name="username" id="username" required="true">
|
||||
<input type="text" class="form-control login-text-input login-input" placeholder="{{ translate('login', 'username', data['lang']) }}" name="username" id="username" required="true">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="label">{{ translate('login', 'password') }}</label>
|
||||
<label class="label">{{ translate('login', 'password', data['lang']) }}</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control login-text-input login-input" placeholder="{{ translate('login', 'password') }}" name="password" id="password" required="true">
|
||||
<input type="password" class="form-control login-text-input login-input" placeholder="{{ translate('login', 'password', data['lang']) }}" name="password" id="password" required="true">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login') }}</button>
|
||||
<button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login', data['lang']) }}</button>
|
||||
</div>
|
||||
<div class="form-group d-flex justify-content-between">
|
||||
<div class="form-check form-check-flat mt-0">
|
||||
|
||||
</div>
|
||||
<a href="#" class="text-small forgot-password ">{{ translate('login', 'forgotPassword') }}</a>
|
||||
<a href="#" class="text-small forgot-password ">{{ translate('login', 'forgotPassword', data['lang']) }}</a>
|
||||
</div>
|
||||
|
||||
<div class="text-block text-center my-3">
|
||||
|
85
app/frontend/templates/public/status.html
Normal file
@ -0,0 +1,85 @@
|
||||
{% extends ../public_base.html %}
|
||||
|
||||
{% block meta %}
|
||||
<meta http-equiv="refresh" content="30">
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content-wrapper col-md login-modal" style="background-color: #222437;">
|
||||
<img src="/static/assets/images/logo_long.png" style='width: 25%; margin-left: 38%;'>
|
||||
<hr />
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<th>{{ translate('dashboard', 'server', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'players', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'motd', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'version', data['lang']) }}</th>
|
||||
<th>{{ translate('dashboard', 'status', data['lang']) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for server in data['servers'] %}
|
||||
<tr>
|
||||
<td>
|
||||
<i class="fas fa-server"></i>
|
||||
{{ server['server_data']['server_name'] }}
|
||||
</td>
|
||||
{% if server['stats']['int_ping_results'] != 'False' %}
|
||||
<td>
|
||||
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max', data['lang']) }}<br />
|
||||
</td>
|
||||
<td>
|
||||
{% if server['stats']['desc'] != 'False' %}
|
||||
{% if server['raw_ping_result']['icon'] %}
|
||||
<img src="data:image/png;base64,{% raw server['raw_ping_result']['icon'] %}" alt="icon"/>
|
||||
{% else %}
|
||||
<img src="/static/assets/images/pack.png" alt="icon" />
|
||||
{% end %}
|
||||
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{ server['stats']['desc'] }}</span> <br />
|
||||
{% end %}
|
||||
</td>
|
||||
<td>
|
||||
{% if server['stats']['version'] != 'False' %}
|
||||
{{ server['stats']['version'] }}
|
||||
{% end %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td colspan="3">
|
||||
<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Crafty can't get infos from this Server </span>
|
||||
</td>
|
||||
{% end %}
|
||||
<td>
|
||||
{% if server['stats']['running'] %}
|
||||
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang']) }}</span>
|
||||
{% else %}
|
||||
<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang']) }}</span>
|
||||
{% end %}
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
|
||||
<script src="/static/assets/js/motd.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
|
||||
for (element of all_motds) {
|
||||
initParser(element.id, element.id);
|
||||
};
|
||||
}());
|
||||
</script>
|
||||
|
||||
{% end %}
|
59
app/frontend/templates/public_base.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
{% block meta %}{% end %}
|
||||
<title>{% block title %}{{ _('Default') }}{% end %}</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
|
||||
<!-- endinject -->
|
||||
<!-- Plugin css for this page -->
|
||||
<!-- End Plugin css for this page -->
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<div class="container-scroller">
|
||||
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
|
||||
<div class="row w-100">
|
||||
<div class="mx-auto">
|
||||
<div class="auto-form-wrapper">
|
||||
{% block content %}
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
</div>
|
||||
<!-- page-body-wrapper ends -->
|
||||
</div>
|
||||
<!-- container-scroller -->
|
||||
<!-- plugins:js -->
|
||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||
<!-- endinject -->
|
||||
<!-- inject:js -->
|
||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||
<script src="/static/assets/js/shared/misc.js"></script>
|
||||
<script src="/static/assets/js/shared/settings.js"></script>
|
||||
<script src="/static/assets/js/shared/todolist.js"></script>
|
||||
<!-- endinject -->
|
||||
|
||||
{% block js %}
|
||||
<!-- Custom js for this page -->
|
||||
<!-- End custom js for this page -->
|
||||
{% end %}
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,262 +1,438 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverWizard', 'newServer') }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'newServer') }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post">
|
||||
{% raw xsrf_form_html() %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<h4 class="card-title" style="margin-bottom:20px;">{{ translate('serverWizard', 'serverType') }}</h4>
|
||||
|
||||
<select class="form-control form-control-lg select-css" id="server" name="server">
|
||||
{% for s in data['server_types'] %}
|
||||
{% for v in data['server_types'][s] %}
|
||||
<option value="{{ s }}|{{ v }}">{{ s.capitalize() }} - {{ v }}</option>
|
||||
{% end %}
|
||||
{% end %}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName') }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer') }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings') }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription') }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label>
|
||||
<input type="number" class="form-control" id="port" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg()">{{ translate('serverWizard', 'buildServer') }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm') }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importServer') }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_jar" name="create_type">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName') }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer') }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'serverPath') }} <small>{{ translate('serverWizard', 'absoluteServerPath') }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar') }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings') }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription') }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label>
|
||||
<input type="number" class="form-control" id="port" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton') }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm') }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-13 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importZip') }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_zip" name="create_type">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName') }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer') }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'zipPath') }} <small>{{ translate('serverWizard', 'absoluteZipPath') }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar') }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-sm-3">
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings') }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription') }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="min_memory">{{ translate('serverWizard', 'minMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="max_memory">{{ translate('serverWizard', 'maxMem') }} <small> - {{ translate('serverWizard', 'sizeInGB') }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="port">{{ translate('serverWizard', 'serverPort') }} <small> - {{ translate('serverWizard', 'defaultPort') }}</small></label>
|
||||
<input type="number" class="form-control" id="port" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton') }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js%}
|
||||
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
console.log('ready');
|
||||
$("#min_memory").change(function(){
|
||||
check_sizes('min');
|
||||
});
|
||||
$("#max_memory").change(function(){
|
||||
check_sizes('max');
|
||||
});
|
||||
});
|
||||
|
||||
function wait_msg(importing){
|
||||
bootbox.alert({
|
||||
title: importing ? '{% raw translate("serverWizard", "importing") %}' : '{% raw translate("serverWizard", "downloading") %}',
|
||||
message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient") %}'
|
||||
});
|
||||
}
|
||||
|
||||
function check_sizes(changed){
|
||||
max_mem = parseFloat($('#max_memory').val());
|
||||
min_mem = parseFloat($('#min_memory').val());
|
||||
if (max_mem < min_mem && changed === 'min'){
|
||||
$('#max_memory').val(min_mem)
|
||||
}
|
||||
if (max_mem < min_mem && changed === 'max'){
|
||||
$('#min_memory').val(max_mem)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block title %}Crafty Controller - {{ translate('serverWizard', 'newServer', data['lang']) }}{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="content-wrapper">
|
||||
<div class="d-none" id="overlay" onclick="hide(event)"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard">
|
||||
{% raw xsrf_form_html() %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_type">{{ translate('serverWizard', 'serverType', data['lang']) }}</label>
|
||||
<select class="form-control form-control-lg select-css" id="server_type" name="server_type" onchange="serverTypeChange(this)">
|
||||
<option value="empty">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
|
||||
{% for s in data['server_types'] %}
|
||||
<option value="{{ s }}">{{ s.capitalize() }}</option>
|
||||
{% end %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_version">{{ translate('serverWizard', 'serverVersion', data['lang']) }}</label>
|
||||
<select class="form-control form-control-lg select-css" id="server" name="server">
|
||||
<option value="0">{{ translate('serverWizard', 'selectVersion', data['lang']) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory1">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory1" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory1">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory1" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port1">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port1" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-1">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-1">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-1" aria-expanded="true" aria-controls="collapseRole-1">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-1" class="collapse" aria-labelledby="Role-1" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg()">{{ translate('serverWizard', 'buildServer', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_jar" name="create_type">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'serverPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteServerPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="min_memory2">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory2" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="max_memory2">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory2" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 offset-1">
|
||||
<div class="form-group">
|
||||
<label for="port2">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port2" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-2">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-2">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-2" aria-expanded="true" aria-controls="collapseRole-2">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-2" class="collapse" aria-labelledby="Role-2" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-13 grid-margin stretch-card">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<h4>{{ translate('serverWizard', 'importZip', data['lang']) }}</h4>
|
||||
<br />
|
||||
<p class="card-description">
|
||||
|
||||
<form method="post" class="server-wizard">
|
||||
{% raw xsrf_form_html() %}
|
||||
<input type="hidden" value="import_zip" name="create_type">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_name" name="server_name" value="" placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server">{{ translate('serverWizard', 'zipPath', data['lang']) }} <small>{{ translate('serverWizard', 'absoluteZipPath', data['lang']) }}</small></label>
|
||||
<input type="text" class="form-control" id="server_path" name="server_path" placeholder="/var/opt/server.zip">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
|
||||
<input type="text" class="form-control" id="server_jar" name="server_jar" value="" placeholder="paper.jar">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-sm-3">
|
||||
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}</small></h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="min_memory3" name="min_memory" value="1" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{ translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2" step="0.5" min="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{ translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
|
||||
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<div id="accordion-3">
|
||||
<div class="card">
|
||||
<div class="card-header p-2" id="Role-3">
|
||||
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3" aria-expanded="true" aria-controls="collapseRole-3">
|
||||
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole', data['lang']) }} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate', data['lang']) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
|
||||
<div class="card-body scroll">
|
||||
<div class="form-group">
|
||||
{% for r in data['roles'] %}
|
||||
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}" type="checkbox">
|
||||
{{ r['role_name'].capitalize() }}</label></span>
|
||||
{% end %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mr-2" onclick="wait_msg(true)">{{ translate('serverWizard', 'importServerButton', data['lang']) }}</button>
|
||||
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang']) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.scroll {
|
||||
max-height: 12em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.menu-btn {
|
||||
font-size: 0.9em;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
.menu {
|
||||
padding-top: 10px;
|
||||
z-index: 200;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
background-color: #2a2c44;
|
||||
}
|
||||
.menu-option {
|
||||
padding: 6px 20px 6px;
|
||||
color: white;
|
||||
}
|
||||
#overlay {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% end %}
|
||||
|
||||
{% block js%}
|
||||
|
||||
<script>
|
||||
function dropDown(event) {
|
||||
event.target.parentElement.children[1].classList.remove("d-none");
|
||||
document.getElementById("overlay").classList.remove("d-none");
|
||||
}
|
||||
function hide(event) {
|
||||
var items = document.getElementsByClassName('menu');
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].classList.add("d-none");
|
||||
}
|
||||
document.getElementById("overlay").classList.add("d-none");
|
||||
}
|
||||
$( document ).ready(function() {
|
||||
console.log('ready');
|
||||
var forms = $('form.server-wizard');
|
||||
forms.each(function(i, formEl) {
|
||||
var form = $(formEl);
|
||||
var min = form.find('[name=min_memory]');
|
||||
var max = form.find('[name=max_memory]');
|
||||
console.log(form, min, max)
|
||||
min.change(function(){
|
||||
check_sizes(max, min, 'min');
|
||||
});
|
||||
max.change(function(){
|
||||
check_sizes(max, min, 'max');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function wait_msg(importing){
|
||||
bootbox.alert({
|
||||
title: importing ? '{% raw translate("serverWizard", "importing", data['lang']) %}' : '{% raw translate("serverWizard", "downloading", data['lang']) %}',
|
||||
message: '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data['lang']) %}'
|
||||
});
|
||||
}
|
||||
|
||||
function check_sizes(a, b, changed){
|
||||
max_mem = parseFloat(a.val());
|
||||
min_mem = parseFloat(b.val());
|
||||
if (max_mem < min_mem && changed === 'min'){
|
||||
a.val(min_mem)
|
||||
}
|
||||
if (max_mem < min_mem && changed === 'max'){
|
||||
b.val(max_mem)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
// array of possible countries in the same order as they appear in the country selection list
|
||||
|
||||
function decodeHtmlCharCodes(str) {
|
||||
return str.replace(""", "\"");
|
||||
}
|
||||
|
||||
function convertHtmlJsonToJavacriptArray(str) {
|
||||
var result = []
|
||||
str = decodeHtmlCharCodes(str)
|
||||
for(var i in str)
|
||||
result.push([i, str [i]]);
|
||||
return result
|
||||
}
|
||||
|
||||
var text = '{% raw data["js_server_types"] %}';
|
||||
var serverTypesLists = JSON.parse(text);
|
||||
//convertHtmlJsonToJavacriptArray('{{ data["js_server_types"] }}')
|
||||
/* CountryChange() is called from the onchange event of a select element.
|
||||
* param selectObj - the select object which fired the on change event.
|
||||
*/
|
||||
function serverTypeChange(selectObj) {
|
||||
// get the index of the selected option
|
||||
var idx = selectObj.selectedIndex;
|
||||
// get the value of the selected option
|
||||
var which = selectObj.options[idx].value;
|
||||
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
||||
cList = serverTypesLists[which];
|
||||
// get the country select element via its known id
|
||||
var cSelect = document.getElementById("server");
|
||||
// remove the current options from the country select
|
||||
var len=cSelect.options.length;
|
||||
while (cSelect.options.length > 0) {
|
||||
cSelect.remove(0);
|
||||
}
|
||||
var newOption;
|
||||
// create new options ordered by descending
|
||||
for (var i=(cList.length)-1; i>=0; i--) {
|
||||
newOption = document.createElement("option");
|
||||
newOption.value = which+"|"+cList[i]; // assumes option string and value are the same
|
||||
newOption.text=cList[i];
|
||||
// add the new option
|
||||
try {
|
||||
cSelect.add(newOption); // this will fail in DOM browsers but is needed for IE
|
||||
}
|
||||
catch (e) {
|
||||
cSelect.appendChild(newOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
{% end %}
|
@ -56,7 +56,7 @@
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary submit-btn btn-block">Save</button>
|
||||
<button class="btn btn-primary submit-btn btn-block"><i class="fas fa-save"></i> Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
215
app/migrations/20210813111015_init.py
Normal file
@ -0,0 +1,215 @@
|
||||
import peewee
|
||||
import datetime
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
db = database
|
||||
class Users(peewee.Model):
|
||||
user_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_login = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_update = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_ip = peewee.CharField(default="")
|
||||
username = peewee.CharField(default="", unique=True, index=True)
|
||||
password = peewee.CharField(default="")
|
||||
enabled = peewee.BooleanField(default=True)
|
||||
superuser = peewee.BooleanField(default=False)
|
||||
# we may need to revisit this
|
||||
api_token = peewee.CharField(default="", unique=True, index=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "users"
|
||||
database = db
|
||||
|
||||
class Roles(peewee.Model):
|
||||
role_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
last_update = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
role_name = peewee.CharField(default="", unique=True, index=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "roles"
|
||||
database = db
|
||||
|
||||
class User_Roles(peewee.Model):
|
||||
user_id = peewee.ForeignKeyField(Users, backref='user_role')
|
||||
role_id = peewee.ForeignKeyField(Roles, backref='user_role')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_roles'
|
||||
primary_key = peewee.CompositeKey('user_id', 'role_id')
|
||||
database = db
|
||||
|
||||
class Audit_Log(peewee.Model):
|
||||
audit_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
user_name = peewee.CharField(default="")
|
||||
user_id = peewee.IntegerField(default=0, index=True)
|
||||
source_ip = peewee.CharField(default='127.0.0.1')
|
||||
# When auditing global events, use server ID 0
|
||||
server_id = peewee.IntegerField(default=None, index=True)
|
||||
log_msg = peewee.TextField(default='')
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
class Host_Stats(peewee.Model):
|
||||
time = peewee.DateTimeField(default=datetime.datetime.now, index=True)
|
||||
boot_time = peewee.CharField(default="")
|
||||
cpu_usage = peewee.FloatField(default=0)
|
||||
cpu_cores = peewee.IntegerField(default=0)
|
||||
cpu_cur_freq = peewee.FloatField(default=0)
|
||||
cpu_max_freq = peewee.FloatField(default=0)
|
||||
mem_percent = peewee.FloatField(default=0)
|
||||
mem_usage = peewee.CharField(default="")
|
||||
mem_total = peewee.CharField(default="")
|
||||
disk_json = peewee.TextField(default="")
|
||||
|
||||
class Meta:
|
||||
table_name = "host_stats"
|
||||
database = db
|
||||
|
||||
class Servers(peewee.Model):
|
||||
server_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
server_uuid = peewee.CharField(default="", index=True)
|
||||
server_name = peewee.CharField(default="Server", index=True)
|
||||
path = peewee.CharField(default="")
|
||||
backup_path = peewee.CharField(default="")
|
||||
executable = peewee.CharField(default="")
|
||||
log_path = peewee.CharField(default="")
|
||||
execution_command = peewee.CharField(default="")
|
||||
auto_start = peewee.BooleanField(default=0)
|
||||
auto_start_delay = peewee.IntegerField(default=10)
|
||||
crash_detection = peewee.BooleanField(default=0)
|
||||
stop_command = peewee.CharField(default="stop")
|
||||
executable_update_url = peewee.CharField(default="")
|
||||
server_ip = peewee.CharField(default="127.0.0.1")
|
||||
server_port = peewee.IntegerField(default=25565)
|
||||
logs_delete_after = peewee.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
table_name = "servers"
|
||||
database = db
|
||||
|
||||
class User_Servers(peewee.Model):
|
||||
user_id = peewee.ForeignKeyField(Users, backref='user_server')
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='user_server')
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_servers'
|
||||
primary_key = peewee.CompositeKey('user_id', 'server_id')
|
||||
database = db
|
||||
|
||||
class Role_Servers(peewee.Model):
|
||||
role_id = peewee.ForeignKeyField(Roles, backref='role_server')
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='role_server')
|
||||
|
||||
class Meta:
|
||||
table_name = 'role_servers'
|
||||
primary_key = peewee.CompositeKey('role_id', 'server_id')
|
||||
database = db
|
||||
|
||||
class Server_Stats(peewee.Model):
|
||||
stats_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='server', index=True)
|
||||
started = peewee.CharField(default="")
|
||||
running = peewee.BooleanField(default=False)
|
||||
cpu = peewee.FloatField(default=0)
|
||||
mem = peewee.FloatField(default=0)
|
||||
mem_percent = peewee.FloatField(default=0)
|
||||
world_name = peewee.CharField(default="")
|
||||
world_size = peewee.CharField(default="")
|
||||
server_port = peewee.IntegerField(default=25565)
|
||||
int_ping_results = peewee.CharField(default="")
|
||||
online = peewee.IntegerField(default=0)
|
||||
max = peewee.IntegerField(default=0)
|
||||
players = peewee.CharField(default="")
|
||||
desc = peewee.CharField(default="Unable to Connect")
|
||||
version = peewee.CharField(default="")
|
||||
updating = peewee.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "server_stats"
|
||||
database = db
|
||||
|
||||
class Commands(peewee.Model):
|
||||
command_id = peewee.AutoField()
|
||||
created = peewee.DateTimeField(default=datetime.datetime.now)
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='server', index=True)
|
||||
user = peewee.ForeignKeyField(Users, backref='user', index=True)
|
||||
source_ip = peewee.CharField(default='127.0.0.1')
|
||||
command = peewee.CharField(default='')
|
||||
executed = peewee.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "commands"
|
||||
database = db
|
||||
|
||||
class Webhooks(peewee.Model):
|
||||
id = peewee.AutoField()
|
||||
name = peewee.CharField(max_length=64, unique=True, index=True)
|
||||
method = peewee.CharField(default="POST")
|
||||
url = peewee.CharField(unique=True)
|
||||
event = peewee.CharField(default="")
|
||||
send_data = peewee.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "webhooks"
|
||||
database = db
|
||||
|
||||
class Schedules(peewee.Model):
|
||||
schedule_id = peewee.IntegerField(unique=True, primary_key=True)
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='schedule_server')
|
||||
enabled = peewee.BooleanField()
|
||||
action = peewee.CharField()
|
||||
interval = peewee.IntegerField()
|
||||
interval_type = peewee.CharField()
|
||||
start_time = peewee.CharField(null=True)
|
||||
command = peewee.CharField(null=True)
|
||||
comment = peewee.CharField()
|
||||
|
||||
class Meta:
|
||||
table_name = 'schedules'
|
||||
database = db
|
||||
|
||||
class Backups(peewee.Model):
|
||||
directories = peewee.CharField(null=True)
|
||||
max_backups = peewee.IntegerField()
|
||||
server_id = peewee.ForeignKeyField(Servers, backref='backups_server')
|
||||
schedule_id = peewee.ForeignKeyField(Schedules, backref='backups_schedule')
|
||||
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
database = db
|
||||
|
||||
migrator.create_table(Backups)
|
||||
migrator.create_table(Users)
|
||||
migrator.create_table(Roles)
|
||||
migrator.create_table(User_Roles)
|
||||
migrator.create_table(User_Servers)
|
||||
migrator.create_table(Host_Stats)
|
||||
migrator.create_table(Webhooks)
|
||||
migrator.create_table(Servers)
|
||||
migrator.create_table(Role_Servers)
|
||||
migrator.create_table(Server_Stats)
|
||||
migrator.create_table(Commands)
|
||||
migrator.create_table(Audit_Log)
|
||||
migrator.create_table(Schedules)
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_table('users')
|
||||
migrator.drop_table('roles')
|
||||
migrator.drop_table('user_roles')
|
||||
migrator.drop_table('audit_log') # ? Not 100% sure of the table name, please specify in the schema
|
||||
migrator.drop_table('host_stats')
|
||||
migrator.drop_table('servers')
|
||||
migrator.drop_table('user_servers')
|
||||
migrator.drop_table('role_servers')
|
||||
migrator.drop_table('server_stats')
|
||||
migrator.drop_table('commands')
|
||||
migrator.drop_table('webhooks')
|
||||
migrator.drop_table('schedules')
|
||||
migrator.drop_table('backups')
|
17
app/migrations/20210819155737_permissions.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('user_servers', permissions=peewee.CharField(default='00000000')) # First argument can be model class OR table name
|
||||
migrator.add_columns('role_servers', permissions=peewee.CharField(default='00000000')) # First argument can be model class OR table name
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('user_servers', ['permissions']) # First argument can be model class OR table name
|
||||
migrator.drop_columns('role_servers', ['permissions']) # First argument can be model class OR table name
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
27
app/migrations/20210822092240_crafty_permissions.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Generated by database migrator
|
||||
from peewee import *
|
||||
from app.classes.models.users import Users
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
db = database
|
||||
class User_Crafty(Model):
|
||||
user_id = ForeignKeyField(Users, backref='users_crafty')
|
||||
permissions = CharField(default="00000000")
|
||||
limit_server_creation = IntegerField(default=-1)
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_crafty'
|
||||
database = db
|
||||
migrator.create_table(User_Crafty)
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_table('user_crafty') # Can be model class OR table name
|
||||
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
29
app/migrations/20210822101530_delete_User_Servers.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Generated by database migrator
|
||||
from peewee import *
|
||||
from app.classes.models.users import Users
|
||||
from app.classes.models.servers import Servers
|
||||
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.drop_table('user_servers') # Can be model class OR table name
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
db = database
|
||||
class User_Servers(Model):
|
||||
user_id = ForeignKeyField(Users, backref='user_server')
|
||||
server_id = ForeignKeyField(Servers, backref='user_server')
|
||||
permissions = CharField(default="00000000")
|
||||
|
||||
class Meta:
|
||||
table_name = 'user_servers'
|
||||
primary_key = CompositeKey('user_id', 'server_id')
|
||||
database = db
|
||||
migrator.create_table(User_Servers)
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
24
app/migrations/20210824205501_permissions_limits_1.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('user_crafty', limit_user_creation=peewee.IntegerField(default=0))
|
||||
migrator.add_columns('user_crafty', limit_role_creation=peewee.IntegerField(default=0))
|
||||
migrator.add_columns('user_crafty', created_server=peewee.IntegerField(default=0))
|
||||
migrator.add_columns('user_crafty', created_user=peewee.IntegerField(default=0))
|
||||
migrator.add_columns('user_crafty', created_role=peewee.IntegerField(default=0))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('user_crafty', ['limit_user_creation'])
|
||||
migrator.drop_columns('user_crafty', ['limit_role_creation'])
|
||||
migrator.drop_columns('user_crafty', ['created_server'])
|
||||
migrator.drop_columns('user_crafty', ['created_user'])
|
||||
migrator.drop_columns('user_crafty', ['created_role'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|
16
app/migrations/20210915205501_waiting_start_1.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by database migrator
|
||||
import peewee
|
||||
|
||||
def migrate(migrator, database, **kwargs):
|
||||
migrator.add_columns('server_stats', waiting_start=peewee.BooleanField(default=False))
|
||||
"""
|
||||
Write your migrations here.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator, database, **kwargs):
|
||||
migrator.drop_columns('server_stats', ['waiting_start'])
|
||||
"""
|
||||
Write your rollback migrations here.
|
||||
"""
|