Merge branch 'dev' into 'master'

Huge commit for Alpha 2.5/Some of 3

See merge request crafty-controller/crafty-commander!70
This commit is contained in:
xithical 2021-11-20 17:47:22 +00:00
commit 87fcb60a24
112 changed files with 8013 additions and 2606 deletions

9
.dockerignore Normal file
View File

@ -0,0 +1,9 @@
docker/
.git/
.gitignore
crafty_commander.exe
DBCHANGES.md
docker-compose.yml.example
README.md

4
.gitignore vendored
View File

@ -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
View 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
View 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
View 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
View 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 | [![pipeline status](https://gitlab.com/crafty-controller/crafty-commander/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-commander/-/commits/master) |
| :dev | [![pipeline status](https://gitlab.com/crafty-controller/crafty-commander/badges/dev/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-commander/-/commits/dev) |
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.

View 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)

View 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)

View 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 {}

View 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

View 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)

View 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)

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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()

View 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()

View 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()

View 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()

View 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()

View 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
View 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()

View File

@ -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")

View File

@ -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)

View File

@ -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()

View File

@ -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)

View 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()

View 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))

View File

@ -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()

View File

@ -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

View File

@ -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')

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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

View 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))

View 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))

View File

@ -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())

View File

@ -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")

View File

@ -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,
)
)

View 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'})

View 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,
)

View File

@ -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):

View 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)

View File

@ -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)

View File

@ -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')

View File

@ -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"]
}
}

View File

@ -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
}

View File

@ -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"
}
},

View 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"
}

View File

@ -2,5 +2,5 @@
"major": 4,
"minor": 0,
"sub": 0,
"meta": "alpha.2"
"meta": "alpha.3"
}

View File

@ -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;
}

View File

@ -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; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 413 KiB

After

Width:  |  Height:  |  Size: 413 KiB

View File

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 243 KiB

View File

Before

Width:  |  Height:  |  Size: 558 KiB

After

Width:  |  Height:  |  Size: 558 KiB

View File

Before

Width:  |  Height:  |  Size: 842 KiB

After

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 856 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View 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);
}
}

View File

@ -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');

View File

@ -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>

View File

@ -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>

View File

@ -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>&nbsp;
<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> &nbsp;
<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> &nbsp; {{ 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> &nbsp; {{ 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> &nbsp; {{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> &nbsp;
<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> &nbsp;
<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> &nbsp;
<span class="menu-title">{{ translate('sidebar', 'contribute') }}</span>
<span class="menu-title">{{ translate('sidebar', 'contribute', data['lang']) }}</span>
</a>
</li>
{% end %}

View File

@ -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>

View File

@ -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>&nbsp; to our Patreon supporters </p>
<table class="table">
<p class="card-description"> A huge <code>thank you</code>&nbsp; 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>&nbsp; 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 %}

View File

@ -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> &nbsp;{{ 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> &nbsp; {{ translate('dashboard', 'newServer') }}</a></div>
<h4 class="card-title"><i class="fas fa-server"></i> &nbsp;{{ 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> &nbsp; {{ 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> &nbsp;
<a class="restart_button" data-id="{{server['server_data']['server_id']}}"> <i class="fas fa-sync"></i></a> &nbsp;
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"></i></a> &nbsp;
<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> &nbsp;
<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> &nbsp;
<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> &nbsp;
{% 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> &nbsp;
<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>&nbsp;
<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> &nbsp;
{% 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> &nbsp; {% raw translate("dashboard", "bePatientStart") %} </div>'
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% 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> &nbsp; {% raw translate("dashboard", "bePatientStop") %} </div>'
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% 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> &nbsp; {% raw translate("dashboard", "bePatientRestart") %} </div>'
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% 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> &nbsp; {% raw translate("dashboard", "bePatientClone") %} </div>'
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data['lang']) %} </div>'
});
});

View File

@ -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>

View File

@ -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> &nbsp; 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> &nbsp; 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 %}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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'] }} );
}
}
});

View File

@ -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> &nbsp; {% 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> &nbsp; {% 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> &nbsp; {% 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>

View File

@ -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%';">&times;</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';
});
}
});

View File

@ -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">

View File

@ -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());

View File

@ -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>

View File

@ -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>

View File

@ -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">
&nbsp;
</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">

View 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 %}

View 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>

View File

@ -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">&nbsp;
{{ 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">&nbsp;
{{ 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">&nbsp;
{{ 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("&quot;", "\"");
}
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 %}

View File

@ -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>

View 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')

View 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.
"""

View 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.
"""

View 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.
"""

View 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.
"""

View 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.
"""

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